diff --git a/.clang-format b/.clang-format index b41d0c25a..39b753e7a 100644 --- a/.clang-format +++ b/.clang-format @@ -56,7 +56,6 @@ DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - - foreach - Q_FOREACH - BOOST_FOREACH diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87310899f..ac09746fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,12 +61,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: repo - name: Cache $CCACHE_DIR - uses: actions/cache@v2 + uses: actions/cache@v3 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache with: @@ -86,7 +86,7 @@ jobs: run: tar --posix -c -z -f ${{ env.VERILATOR_ARCHIVE }} repo - name: Upload tar archive - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: ${{ github.workspace }}/${{ env.VERILATOR_ARCHIVE }} name: ${{ env.VERILATOR_ARCHIVE }} @@ -132,7 +132,7 @@ jobs: steps: - name: Download tar archive - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: ${{ env.VERILATOR_ARCHIVE }} path: ${{ github.workspace }} @@ -142,7 +142,7 @@ jobs: run: tar -x -z -f ${{ env.VERILATOR_ARCHIVE }} - name: Cache $CCACHE_DIR - uses: actions/cache@v2 + uses: actions/cache@v3 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache with: diff --git a/.github/workflows/contributor.yml b/.github/workflows/contributor.yml index bbc00ce97..e40acd32d 100644 --- a/.github/workflows/contributor.yml +++ b/.github/workflows/contributor.yml @@ -13,5 +13,5 @@ jobs: name: "'docs/CONTRIBUTORS' was signed" runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: test_regress/t/t_dist_contributors.pl diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 506dd98a1..4dcffead4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -24,14 +24,14 @@ jobs: Build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: CI_BUILD_STAGE_NAME: build - CI_RUNS_ON: ubuntu-20.04 + CI_RUNS_ON: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: repo @@ -46,7 +46,7 @@ jobs: run: tar --posix -c -z -f ${{ env.VERILATOR_ARCHIVE }} repo - name: Upload tar archive - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: ${{ github.workspace }}/${{ env.VERILATOR_ARCHIVE }} name: ${{ env.VERILATOR_ARCHIVE }} @@ -73,15 +73,15 @@ jobs: - 9 include: - { test: dist, num: '' } - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: test-${{ matrix.test }}${{ matrix.num }} env: CI_BUILD_STAGE_NAME: test - CI_RUNS_ON: ubuntu-20.04 + CI_RUNS_ON: ubuntu-22.04 steps: - name: Download tar archive - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: ${{ env.VERILATOR_ARCHIVE }} path: ${{ github.workspace }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2b903aa88..3aa88e6a6 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -19,7 +19,7 @@ jobs: CI_M32: 0 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Install packages for build diff --git a/Changes b/Changes index 06ce093d1..7a64aee94 100644 --- a/Changes +++ b/Changes @@ -8,29 +8,75 @@ The changes in each Verilator version are described below. The contributors that suggested a given feature are shown in []. Thanks! +Verilator 5.002 2022-10-29 +========================== + +**Major:** + +* This is a major new release. +* Require C++20 for the new --timing features. Upgrading to a C++20 or + newer compiler is strongly recommended. +* Support the Active and NBA scheduling regions as defined by the + SystemVerilog standard (IEEE 1800-2017 chapter 4). This means all generated + clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD] +* Support timing controls (delays, event controls in any location, wait + statements) and forks. [Krzysztof Bieganski, Antmicro Ltd] + This may require adding --timing or --no-timing. See docs for details. +* Introduce a new combinational logic optimizer (DFG), that can yield + significant performance improvements on some designs. [Geza Lore, Shunyao CAD] +* Add --binary option as alias of --main --exe --build --timing (#3625). + For designs where C++ was only used to make a simple no-I/O testbench, we + recommend abandoning that C++, and instead letting Verilator build it + with --binary (or --main). + +**Minor:** + +* Split UNUSED warning into genvar, param, and signal warnings (#3607). [Topa Topino] +* Support standalone 'this' in classes (#2594) (#3248) (#3675). [Arkadiusz Kozdra, Antmicro Ltd] +* Support tristate select/extend (#3604). [Ryszard Rozak, Antmicro Ltd> +* Support linting for top module interfaces (#3635). [Kanad Kanhere] +* Support virtual interfaces (#3654). [Arkadiusz Kozdra, Antmicro Ltd] +* Support class type params without defaults (#3693). [Krzysztof Bieganski, Antmicro Ltd] +* Support empty generate_regions (#3695). [mpb27] +* Support access to constructs inside type parameters (#3702). [Arkadiusz Kozdra, Antmicro Ltd] +* Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] +* Add --get-supported to determine what features are in Verilator. +* Add error on real edge event control. +* Fix false LATCH warning on 'unique if' (#3088). [Rachit Nigam] +* Fix cell assigning integer array parameters (#3299). [Michael Platzer] +* Fix LSB error on --hierarchical submodules (#3539). [danbone] +* Fix $display of fixed-width numbers (#3565). [Iztok Jeras] +* Fix foreach and pre/post increment in functions (#3613). [Nandu Raj] +* Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd] +* Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] +* Fix VPI inline module naming mismatch (#3690) (#3694). [Jiuyang Liu] +* Fix deadlock in timeprecision when using systemC (#3707). [Kamil Rakoczy, Antmicro Ltd] +* Fix width mismatch on inside operator (#3714). [Alex Torregrosa] + + Verilator 4.228 2022-10-01 ========================== **Announcement:** -* The next release is anticipated primere Verilator Version 5. Please +* The next release is anticipated to premiere Verilator Version 5. Please consider beta-testing the github 'develop-v5' branch, which will soon merge into the github 'master' branch (#3383). **Minor:** -* Support some IEEE signal strengths (#3601) (#3629). [Ryszard Rozak/Antmicro] +* Support some IEEE signal strengths (#3601) (#3629). [Ryszard Rozak, Antmicro Ltd] * Add --main to generate main() C++ (previously was experimental only). * Add --build-jobs, and rework arguments for -j (#3623). [Kamil Rakoczy] * Rename --bin to --build-dep-bin. * Rename debug flags --dumpi-tree, --dumpi-graph, etc. [Geza Lore] -* Fix thread saftey in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic] +* Fix thread safety in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic] * Fix crash in gate optimization of circular logic (#3543). [Bill Flynn] * Fix arguments in non-static method call (#3547) (#3582). [Gustav Svensk] * Fix default --mod-prefix when --prefix is repeated (#3603). [Geza Lore] * Fix calling trace() after open() segfault (#3610) (#3627). [Yu-Sheng Lin] -* Fix typedef'ed class conversion to boolean (#3616). [Aleksander Kiryk] -* Fix Verilation speed when disabled warnings (#3632). [Kamil Rakoczy/Antmicro] +* Fix typedef'ed class conversion to Boolean (#3616). [Aleksander Kiryk] +* Fix Verilation speed when disabled warnings (#3632). [Kamil Rakoczy, Antmicro Ltd] Verilator 4.226 2022-08-31 @@ -39,31 +85,31 @@ Verilator 4.226 2022-08-31 **Minor:** * Add --future0 and --future1 options. -* Support class parameters (#2231) (#3541). [Arkadiusz Kozdra/Antmicro] -* Support wildcard index associative arrays (#3501). [Arkadiusz Kozdra/Antmicro] +* Support class parameters (#2231) (#3541). [Arkadiusz Kozdra, Antmicro Ltd] +* Support wildcard index associative arrays (#3501). [Arkadiusz Kozdra, Antmicro Ltd] * Support negated properties (#3572). [Aleksander Kiryk] * Support $test$plusargs(expr) (#3489). * Rename trace rolloverSize() (#3570). * Improve Verilation speed with --threads on large designs. [Geza Lore] -* Improve Verilation memory by reducing V3Number (#3521). [Mariusz Glebocki/Antmicro] +* Improve Verilation memory by reducing V3Number (#3521). [Mariusz Glebocki, Antmicro Ltd] * Fix struct pattern assignment (#2328) (#3517). [Mostafa Gamal] * Fix public combo propagation issues (#2905). [Todd Strader] * Fix incorrect tristate logic (#3399) [shareefj, Vighnesh Iyer] * Fix incorrect bit op tree optimization (#3470). [algrobman] * Fix bisonpre for MSYS2 (#3471). -* Fix max memory usage (#3483). [Kamil Rakoczy/Antmicro] +* Fix max memory usage (#3483). [Kamil Rakoczy, Antmicro Ltd] * Fix empty string arguments to display (#3484). [Grulfen] -* Fix table misoptimizing away display (#3488). [Stefan Post] +* Fix table optimizing away display (#3488). [Stefan Post] * Fix unique_ptr memory header for MinGW64 (#3493). -* Fix $dump systemtask with --output-split-cfuncs (#3495) (#3497). [Varun Koyyalagunta] +* Fix $dump system task with --output-split-cfuncs (#3495) (#3497). [Varun Koyyalagunta] * Fix wrong bit op tree optimization (#3509). [Nathan Graybeal] * Fix nested default assignment for struct pattern (#3511) (#3524). [Mostafa Gamal] * Fix sformat string incorrectly cleared (#3515) (#3519). [Gustav Svensk] -* Fix segfault exporting non-existant package (#3535). +* Fix segfault exporting non-existent package (#3535). * Fix void-cast queue pop_front or pop_back (#3542) (#3364). [Drew Ranck] * Fix case statement comparing string literal (#3544). [Gustav Svensk] -* Fix === with some tristate constants (#3551). [Ryszard Rozak/Antmicro] -* Fix converting subclasses to string (#3552). [Arkadiusz Kozdra/Antmicro] +* Fix === with some tristate constants (#3551). [Ryszard Rozak, Antmicro Ltd] +* Fix converting classes to string (#3552). [Arkadiusz Kozdra, Antmicro Ltd] * Fix --hierarchical with order-based pin connections (#3583) (#3585). [Kelin9298] @@ -85,7 +131,7 @@ Verilator 4.224 2022-06-19 * Improve conditional merging optimization (#3125). [Geza Lore, Shunyao CAD] * Define VM_TRACE_VCD when tracing in VCD format. [Geza Lore, Shunyao CAD] * Add assert when VerilatedContext is mis-deleted (#3121). [Rupert Swarbrick] -* Internal prep work towards timing control. [Krzysztof Bieganski/Antmicro] +* Internal prep work towards timing control. [Krzysztof Bieganski, Antmicro Ltd] * Fix hang with large case statement optimization (#3405). [Mike Urbach] * Fix UNOPTFLAT warning from initial static var (#3406). [Kamil Rakoczy] * Fix compile error when enable VL_LEAK_CHECKS (#3411). [HungMingWu] @@ -252,7 +298,7 @@ Verilator 4.212 2021-09-01 * Fix re-evaluation of logic dependent on state set in DPI exports (#3091). [Geza Lore] * Support unpacked array localparams in tasks/functions (#3078). [Geza Lore] * Support timeunit/timeprecision in $unit. -* Support assignment patterns as children of pins (#3041). [Krzysztof Bieganski/Antmicro] +* Support assignment patterns as children of pins (#3041). [Krzysztof Bieganski, Antmicro Ltd] * Add --instr-count-dpi to tune assumed DPI import cost for multithreaded model scheduling. Default value changed to 200 (#3068). [Yinan Xu] * Output files are split based on the set of headers required @@ -317,7 +363,7 @@ Verilator 4.204 2021-06-12 * Prep work towards better ccache hashing/performance. [Geza Lore] * Fix assertion failure in bitOpTree optimization (#2891) (#2899). [Raynard Qiao] * Fix DPI functions not seen as vpiModule (#2893). [Todd Strader] -* Fix bounds check in VL_SEL_IWII (#2910). [Krzysztof Bieganski/Antmicro] +* Fix bounds check in VL_SEL_IWII (#2910). [Krzysztof Bieganski, Antmicro Ltd] * Fix slowdown in elaboration (#2911). [Nathan Graybeal] * Fix initialization of assoc in assoc array (#2914). [myftptoyman] * Fix make support for gmake 3.x (#2920) (#2921). [Philipp Wagner] @@ -432,7 +478,7 @@ Verilator 4.108 2021-01-10 **Major:** * Many VPI changes for IEEE compatibility, which may alter behavior from previous releases. -* Support randomize() class method and rand (#2607). [Krzysztof Bieganski/Antmicro] +* Support randomize() class method and rand (#2607). [Krzysztof Bieganski, Antmicro Ltd] **Minor:** @@ -492,7 +538,7 @@ Verilator 4.104 2020-11-14 * Support queue and associative array 'with' statements (#2616). * Support queue slicing (#2326). * Support associative array pattern assignments and defaults. -* Support static methods and typedefs in classes (#2615). [Krzysztof Bieganski/Antmicro] +* Support static methods and typedefs in classes (#2615). [Krzysztof Bieganski, Antmicro Ltd] * Add error on typedef referencing self (#2539). [Cody Piersall] * With --debug, turn off address space layout randomization. * Fix iteration over mutating list bug in VPI (#2588). [Kaleb Barrett] diff --git a/Makefile.in b/Makefile.in index 662a57361..a8dd0a7fa 100644 --- a/Makefile.in +++ b/Makefile.in @@ -107,6 +107,7 @@ SUBDIRS = docs src test_regress \ examples/cmake_tracing_c \ examples/cmake_tracing_sc \ examples/cmake_protect_lib \ + examples/make_hello_binary \ examples/make_hello_c \ examples/make_hello_sc \ examples/make_tracing_c \ @@ -243,6 +244,7 @@ installdata: ; for p in $(VL_INST_INC_SRCDIR_FILES) ; do \ $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p; \ done + $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_hello_binary $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_hello_c $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_hello_sc $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_tracing_c @@ -278,6 +280,7 @@ uninstall: -rmdir $(DESTDIR)$(pkgdatadir)/include/gtkwave -rmdir $(DESTDIR)$(pkgdatadir)/include/vltstd -rmdir $(DESTDIR)$(pkgdatadir)/include + -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_hello_binary -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_hello_c -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_hello_sc -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_tracing_c @@ -326,8 +329,7 @@ CLANGTIDY_FLAGS = -config='' \ -header-filter='.*' \ -checks='-fuchsia-*,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-goto,-modernize-avoid-c-arrays,-readability-magic-numbers,-readability-simplify-boolean-expr,-cppcoreguidelines-macro-usage' \ -CLANGTIDY_DEP = $(subst .h,.h.tidy,$(CPPCHECK_H)) \ - $(subst .cpp,.cpp.tidy,$(CPPCHECK_CPP)) +CLANGTIDY_DEP = $(subst .cpp,.cpp.tidy,$(CPPCHECK_CPP)) CLANGTIDY_DEFS = -DVL_DEBUG=1 -DVL_THREADED=1 -DVL_CPPCHECK=1 clang-tidy: $(CLANGTIDY_DEP) diff --git a/bin/verilator b/bin/verilator index 4c9e08209..cd3a25c35 100755 --- a/bin/verilator +++ b/bin/verilator @@ -244,6 +244,7 @@ Verilator - Translate and simulate SystemVerilog code using C++/SystemC verilator --help verilator --version + verilator --binary -j 0 [options] [source_files.v]... [opt_c_files.cpp/c/cc/a/o/so] verilator --cc [options] [source_files.v]... [opt_c_files.cpp/c/cc/a/o/so] verilator --sc [options] [source_files.v]... [opt_c_files.cpp/c/cc/a/o/so] verilator --lint-only -Wall [source_files.v]... @@ -282,6 +283,7 @@ detailed descriptions of these arguments. --autoflush Flush streams after all $displays --bbox-sys Blackbox unknown $system calls --bbox-unsup Blackbox unsupported language features + --binary Build model binary --build Build model executable/library after Verilation --build-dep-bin Override build dependency Verilator binary --build-jobs Parallelism for --build @@ -309,10 +311,12 @@ detailed descriptions of these arguments. +define+= Set preprocessor define --dpi-hdr-only Only produce the DPI header file --dump-defines Show preprocessor defines with -E - --dump-graph Enable dumping V3Graphs to .dot + --dump-dfg Enable dumping DfgGraphs to .dot files + --dump-graph Enable dumping V3Graphs to .dot files --dump-tree Enable dumping Ast .tree files --dump-tree-addrids Use short identifiers instead of addresses --dump- Enable dumping everything in source file + --dumpi-dfg Enable dumping DfgGraphs to .dot files at level --dumpi-graph Enable dumping V3Graphs to .dot files at level --dumpi-tree Enable dumping Ast .tree files at level --dumpi- Enable dumping everything in source file at level @@ -331,6 +335,7 @@ detailed descriptions of these arguments. --gdbbt Run Verilator under GDB for backtrace --generate-key Create random key for --protect-key --getenv Get environment variable with defaults + --get-supported Get if feature is supported --help Display this help --hierarchical Enable hierarchical Verilation -I Directory to search for includes @@ -398,6 +403,8 @@ detailed descriptions of these arguments. --threads Enable multithreading --threads-dpi Enable multithreaded DPI --threads-max-mtasks Tune maximum mtask partitioning + --timing Enable timing support + --no-timing Disable timing support --timescale Sets default timescale --timescale-override Overrides all timescales --top Alias of --top-module diff --git a/configure.ac b/configure.ac index 35b3fcd01..7994e3554 100644 --- a/configure.ac +++ b/configure.ac @@ -10,7 +10,7 @@ # Then 'make maintainer-dist' #AC_INIT([Verilator],[#.### YYYY-MM-DD]) #AC_INIT([Verilator],[#.### devel]) -AC_INIT([Verilator],[4.228 2022-10-01], +AC_INIT([Verilator],[5.002 2022-10-29], [https://verilator.org], [verilator],[https://verilator.org]) @@ -374,6 +374,36 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wlogical-op) _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wthread-safety) AC_SUBST(CFG_CXXFLAGS_WEXTRA) +# Flags for coroutine support for dynamic scheduling +_MY_CXX_CHECK_IFELSE( + -fcoroutines-ts, + [CFG_CXXFLAGS_COROUTINES="-fcoroutines-ts"], + [CFG_CXXFLAGS_COROUTINES="-fcoroutines"]) +AC_SUBST(CFG_CXXFLAGS_COROUTINES) + +# HAVE_COROUTINES +# Check if coroutines are supported at all +AC_MSG_CHECKING([whether coroutines are supported by $CXX]) +ACO_SAVE_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $CFG_CXXFLAGS_COROUTINES" +AC_LINK_IFELSE( + [AC_LANG_PROGRAM([ +#ifdef __clang__ +#define __cpp_impl_coroutine 1 +#endif +#include + ],[[]])], + [_my_result=yes + AC_DEFINE([HAVE_COROUTINES],[1],[Defined if coroutines are supported by $CXX])], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ],[[]])], + [_my_result=yes + AC_DEFINE([HAVE_COROUTINES],[1],[Defined if coroutines are supported by $CXX])], + [_my_result=no])]) +AC_MSG_RESULT($_my_result) +CXXFLAGS="$ACO_SAVE_CXXFLAGS" +AC_SUBST(HAVE_COROUTINES) + # Flags for compiling Verilator internals including parser always _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-Qunused-arguments) _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-faligned-new) diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 0c3be357f..1c78172f7 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -53,6 +53,7 @@ Jamie Iles Jan Van Winkel Jean Berniolles Jeremy Bennett +Jiuyang Liu John Coiner John Demme Jonathan Drolet @@ -71,6 +72,7 @@ Ludwig Rogiers Lukasz Dalek Maarten De Braekeleer Maciej Sobkowski +Marcel Chang Marco Widmer Mariusz Glebocki Markus Krause @@ -117,6 +119,7 @@ Tobias Rosenkranz Tobias Wölfel Todd Strader Tomasz Gorochowik +Topa Topino Tymoteusz Blazejczyk Udi Finkelstein Unai Martinez-Corral diff --git a/docs/gen/ex_DIDNOTCONVERGE_msg.rst b/docs/gen/ex_DIDNOTCONVERGE_msg.rst index c15d71bb4..86af7245a 100644 --- a/docs/gen/ex_DIDNOTCONVERGE_msg.rst +++ b/docs/gen/ex_DIDNOTCONVERGE_msg.rst @@ -1,7 +1,5 @@ .. comment: generated by t_lint_didnotconverge_bad .. code-block:: - -V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request - -V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request_1 - -V{t#,#} CHANGE: t/t_lint_didnotconverge_bad.v:14: a - %Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge + -V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a) + %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. diff --git a/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst b/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst index b6de737df..925494ec9 100644 --- a/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst +++ b/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst @@ -1,4 +1,4 @@ .. comment: generated by t_lint_didnotconverge_nodbg_bad .. code-block:: - %Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge + %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. diff --git a/docs/gen/ex_STMTDLY_msg.rst b/docs/gen/ex_STMTDLY_msg.rst index fe19fb943..15ceefa58 100644 --- a/docs/gen/ex_STMTDLY_msg.rst +++ b/docs/gen/ex_STMTDLY_msg.rst @@ -1,4 +1,4 @@ .. comment: generated by t_lint_stmtdly_bad .. code-block:: - %Warning-STMTDLY: example.v:1:8 Unsupported: Ignoring delay on this delayed statement. + %Warning-STMTDLY: example.v:1:7 Ignoring delay on this statement due to --no-timing diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 443702c7d..638f5c33d 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -94,7 +94,10 @@ commented example. Top level IO signals are read and written as members of the model. You call the model's :code:`eval()` method to evaluate the model. When the simulation is complete call the model's :code:`final()` method to execute -any SystemVerilog final blocks, and complete any assertions. See +any SystemVerilog final blocks, and complete any assertions. If using +:vlopt:`--timing`, there are two additional functions for checking if +there are any events pending in the simulation due to delays, and for +retrieving the simulation time of the next delayed event. See :ref:`Evaluation Loop`. @@ -440,10 +443,25 @@ there is only a single design, you would call :code:`eval_step()` then :code:`eval_end_step()`; in fact :code:`eval()` described above is just a wrapper which calls these two functions. +3. If using delays and :vlopt:`--timing`, there are two additional methods +the user should call: + + * :code:`designp->eventsPending()`, which returns :code:`true` if there are + any delayed events pending, + * :code:`designp->nextTimeSlot()`, which returns the simulation time of the + next delayed event. This method can only be called if + :code:`designp->nextTimeSlot()` returned :code:`true`. + +Call :code:`eventsPending()` to check if you should continue with the +simulation, and then :code:`nextTimeSlot()` to move simulation time forward. +:vlopt:`--main` can be used with :vlopt:`--timing` to generate a basic example +of a timing-enabled eval loop. + When :code:`eval()` (or :code:`eval_step()`) is called Verilator looks for changes in clock signals and evaluates related sequential always blocks, -such as computing always_ff @ (posedge...) outputs. Then Verilator -evaluates combinatorial logic. +such as computing always_ff @ (posedge...) outputs. With :vlopt:`--timing`, it +resumes any delayed processes awaiting the current simulation time. Then +Verilator evaluates combinational logic. Note combinatorial logic is not computed before sequential always blocks are computed (for speed reasons). Therefore it is best to set any non-clock diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index 68b17d671..290619929 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -23,7 +23,8 @@ Contributors Many people have provided ideas and other assistance with Verilator. Verilator is receiving major development support from the `CHIPS Alliance -`_ and `Shunyao CAD `_. +`_, `Antmicro Ltd `_ and +`Shunyao CAD `_. Previous major corporate sponsors of Verilator, by providing significant contributions of time or funds included include: Atmel Corporation, Cavium @@ -32,90 +33,101 @@ Hicamp Systems, Intel Corporation, Mindspeed Technologies Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems Inc., Nauticus Networks Inc., SiCortex Inc, and Shunyao CAD. -The people who have contributed major functionality are: Byron Bradley, -Jeremy Bennett, Lane Brooks, John Coiner, Duane Galbi, Geza Lore, Todd -Strader, Stefan Wallentowitz, Paul Wasson, Jie Xu, and Wilson Snyder. -Major testers included Jeff Dutton, Jonathon Donaldson, Ralf Karge, David -Hewson, Iztok Jeras, Wim Michiels, Alex Solomatnikov, Sebastien Van -Cauwenberghe, Gene Weber, and Clifford Wolf. +The people who have contributed major functionality are: Krzysztof +Bieganski, Byron Bradley, Jeremy Bennett, Lane Brooks, John Coiner, Duane +Galbi, Geza Lore, Todd Strader, Stefan Wallentowitz, Paul Wasson, Jie Xu, +and Wilson Snyder. Major testers included Jeff Dutton, Jonathon Donaldson, +Ralf Karge, David Hewson, Iztok Jeras, Wim Michiels, Alex Solomatnikov, +Sebastien Van Cauwenberghe, Gene Weber, and Clifford Wolf. Some of the people who have provided ideas, and feedback for Verilator -include: David Addison, Nikana Anastasiadis, Vasu Arasanipalai, Jens Arm, -Tariq B. Ahmad, Sharad Bagri, Matthew Ballance, Andrew Bardsley, Matthew -Barr, Geoff Barrett, Kaleb Barrett, Julius Baxter, Jeremy Bennett, Michael -Berman, Jean Berniolles, Victor Besyakov, Moinak Bhattacharyya, Krzysztof -Bieganski, David Binderman, Piotr Binkowski, Johan Bjork, David Black, -Tymoteusz Blazejczyk, Daniel Bone, Morten Borup Petersen, Gregg Bouchard, -Christopher Boumenot, Nick Bowler, Byron Bradley, Bryan Brady, Charlie -Brej, J Briquet, Lane Brooks, John Brownlee, Jeff Bush, Lawrence Butcher, -Tony Bybell, Iru Cai, Ted Campbell, Chris Candler, Lauren Carlson, Donal -Casey, Alex Chadwick, Terry Chen, Yi-Chung Chen, Enzo Chi, Robert A. Clark, -Ryan Clarke, Allan Cochrane, John Coiner, Gianfranco Costamagna, Sean -Cross, George Cuan, Joe DErrico, Lukasz Dalek, Gunter Dannoritzer, Ashutosh -Das, Maarten De Braekeleer, Bernard Deadman, Alberto Del Rio, John Demme, -Mike Denio, John Deroo, Philip Derrick, John Dickol, Ruben Diez, Danny -Ding, Jacko Dirks, Ivan Djordjevic, Jonathon Donaldson, Sebastian Dressler, -Alex Duller, Jeff Dutton, Tomas Dzetkulic, Richard E George, Edgar -E. Iglesias, Usuario Eda, Charles Eddleston, Chandan Egbert, Jan Egil Ruud, -Joe Eiler, Ahmed El-Mahmoudy, Trevor Elbourne, Mats Engstrom, Charles Eric -LaForest, Robert Farrell, Eugen Fekete, Fabrizio Ferrandi, Udi Finkelstein, -Brian Flachs, Andrea Foletto, Bob Fredieu, Duane Galbi, Benjamin Gartner, -Christian Gelinek, Peter Gerst, Glen Gibb, Michael Gielda, Barbara Gigerl, -Shankar Giri, Dan Gisselquist, Petr Gladkikh, Sam Gladstone, Andrew -Goessling, Amir Gonnen, Chitlesh Goorah, Tomasz Gorochowik, Kai Gossner, -Sergi Granell, Al Grant, Alexander Grobman, Xuan Guo, Driss Hafdi, Neil +include: + +David Addison, Tariq B. Ahmad, Nikana Anastasiadis, Vasu Arasanipalai, Jens +Arm, Sharad Bagri, Matthew Ballance, Andrew Bardsley, Matthew Barr, Geoff +Barrett, Kaleb Barrett, Julius Baxter, Jeremy Bennett, Michael Berman, Jean +Berniolles, Victor Besyakov, Moinak Bhattacharyya, Krzysztof Bieganski, +David Binderman, Piotr Binkowski, Johan Bjork, David Black, Tymoteusz +Blazejczyk, Daniel Bone, Gregg Bouchard, Christopher Boumenot, Nick Bowler, +Byron Bradley, Bryan Brady, Maarten De Braekeleer, Charlie Brej, J Briquet, +Lane Brooks, John Brownlee, Jeff Bush, Lawrence Butcher, Tony Bybell, Iru +Cai, Ted Campbell, Chris Candler, Lauren Carlson, Donal Casey, Alex +Chadwick, Marcel Chang, Aliaksei Chapyzhenka, Guokai Chen, Terry Chen, +Yi-Chung Chen, Enzo Chi, Robert A. Clark, Ryan Clarke, Allan Cochrane, John +Coiner, Keith Colbert, Gianfranco Costamagna, Sean Cross, George Cuan, Joe +DErrico, Lukasz Dalek, Gunter Dannoritzer, Ashutosh Das, Bernard Deadman, +John Demme, Mike Denio, John Deroo, Philip Derrick, John Dickol, Ruben +Diez, Danny Ding, Jacko Dirks, Ivan Djordjevic, Jonathon Donaldson, Larry +Doolittle, Sebastian Dressler, Jonathan Drolet, Alex Duller, Jeff Dutton, +Tomas Dzetkulic, Usuario Eda, Charles Eddleston, Chandan Egbert, Joe Eiler, +Ahmed El-Mahmoudy, Trevor Elbourne, Mats Engstrom, Robert Farrell, Eugen +Fekete, Fabrizio Ferrandi, Udi Finkelstein, Brian Flachs, Bill Flynn, +Andrea Foletto, Bob Fredieu, Duane Galbi, Mostafa Gamal, Benjamin Gartner, +Christian Gelinek, Richard E George, Peter Gerst, Glen Gibb, Michael +Gielda, Barbara Gigerl, Shankar Giri, Dan Gisselquist, Petr Gladkikh, Sam +Gladstone, Mariusz Glebocki, Andrew Goessling, Amir Gonnen, Chitlesh +Goorah, Tomasz Gorochowik, Kai Gossner, Sergi Granell, Al Grant, Nathan +Graybeal, Alexander Grobman, Graham Rushton, Xuan Guo, Driss Hafdi, Neil Hamilton, James Hanlon, Oyvind Harboe, Jannis Harder, Junji Hashimoto, Thomas Hawkins, Mitch Hayenga, Harald Heckmann, Robert Henry, Stephen Henry, David Hewson, Jamey Hicks, Joel Holdsworth, Andrew Holme, Hiroki Honda, Alex Hornung, Pierre-Henri Horrein, David Horton, Peter Horvath, Jae -Hossell, Kuoping Hsu, Alan Hunter, James Hutchinson, Anderson Ignacio da -Silva, Jamie Iles, Thomas J Whatson, Ben Jackson, Mark Jackson Pulver, -Shareef Jalloq, Marlon James, Krzysztof Jankowski, HyungKi Jeong, Iztok -Jeras, James Johnson, Christophe Joly, Franck Jullien, James Jung, Mike -Kagen, Arthur Kahlich, Kaalia Kahn, Guy-Armand Kamendje, Vasu Kandadi, -Kanad Kanhere, Patricio Kaplan, Pieter Kapsenberg, Rafal Kapuscik, Ralf -Karge, Dan Katz, Sol Katzman, Ian Kennedy, Michael Killough, Jonathan -Kimmitt, Olof Kindgren, Kevin Kiningham, Dan Kirkham, Sobhan Klnv, Gernot -Koch, Jack Koenig, Soon Koh, Nathan Kohagen, Steve Kolecki, Brett Koonce, -Will Korteland, Wojciech Koszek, Varun Koyyalagunta, Markus Krause, David -Kravitz, Roland Kruse, Andreas Kuster, Sergey Kvachonok, Ed Lander, Steve -Lang, Stephane Laurent, Walter Lavino, Christian Leber, Larry Lee, Igor -Lesik, John Li, Eivind Liland, Charlie Lind, Andrew Ling, Jiuyang Liu, Paul -Liu, Derek Lockhart, Jake Longo, Geza Lore, Arthur Low, Stefan Ludwig, Dan -Lussier, Fred Ma, Duraid Madina, Odd Magne Reitan, Affe Mao, Julien -Margetts, Mark Marshall, Alfonso Martinez, Unai Martinez-Corral, Yves -Mathieu, Patrick Maupin, Conor McCullough, Jason McMullan, Elliot Mednick, -Wim Michiels, Miodrag Milanovic, Peter Monsson, Sean Moore, Dennis -Muhlestein, John Murphy, Matt Myers, Nathan Myers, Richard Myers, Dimitris -Nalbantis, Peter Nelson, Bob Newgard, Paul Nitza, Yossi Nivin, Pete Nixon, -Lisa Noack, Mark Nodine, Kuba Ober, Andreas Olofsson, Baltazar Ortiz, -Aleksander Osman, Don Owen, James Pallister, Vassilis Papaefstathiou, Brad -Parker, Dan Petrisko, Maciej Piechotka, David Pierce, Cody Piersall, -Dominic Plunkett, David Poole, Mike Popoloski, Roman Popov, Rich Porter, -Niranjan Prabhu, Usha Priyadharshini, Prateek Puri, Marshal Qiao, Nandu -Raj, Danilo Ramos, Chris Randall, Anton Rapp, Josh Redford, Frederic -Requin, Dustin Richmond, Samuel Riedel, Eric Rippey, Oleg Rodionov, Ludwig -Rogiers, Paul Rolfe, Arjen Roodselaar, Tobias Rosenkranz, Huang Rui, Denis -Rystsov, John Sanguinetti, Galen Seitz, Joseph Shaker, Salman Sheikh, Yu -Sheng Lin, Hao Shi, Mike Shinkarovsky, Rafael Shirakawa, Jeffrey Short, Fan -Shupei, Rodney Sinclair, Steven Slatter, Brian Small, Garrett Smith, Tim -Snyder, Wilson Snyder, Maciej Sobkowski, Stan Sokorac, Alex Solomatnikov, -Wei Song, Art Stamness, David Stanford, John Stevenson, Pete Stevenson, -Patrick Stewart, Rob Stoddard, Todd Strader, John Stroebel, Sven Stucki, -Howard Su, Emerson Suguimoto, Gene Sullivan, Wai Sum Mong, Qingyao Sun, -Renga Sundararajan, Rupert Swarbrick, Yutetsu Takatsukasa, Thierry Tambe, -Drew Taussig, Peter Tengstrand, Wesley Terpstra, Rui Terra, Stefan Thiede, -Gary Thomas, Ian Thompson, Kevin Thompson, Mike Thyer, Hans Tichelaar, -Viktor Tomov, Steve Tong, Alex Torregrosa, Michael Tresidder, David Turner, -Neil Turton, Cong Van Nguyen, Hans Van Antwerpen, Jan Van Winkel, Sebastien -Van Cauwenberghe, Laurens van Dam, Leendert van Doorn, Srini Vemuri, Yuri +Hossell, Kuoping Hsu, Teng Huang, Steven Hugg, Alan Hunter, James +Hutchinson, Ehab Ibrahim, Edgar E. Iglesias, Jamie Iles, Vighnesh Iyer, Ben +Jackson, Shareef Jalloq, Marlon James, Krzysztof Jankowski, HyungKi Jeong, +Iztok Jeras, Alexandre Joannou, James Johnson, Christophe Joly, Franck +Jullien, James Jung, Mike Kagen, Arthur Kahlich, Kaalia Kahn, Guy-Armand +Kamendje, Vasu Kandadi, Kanad Kanhere, Patricio Kaplan, Pieter Kapsenberg, +Rafal Kapuscik, Ralf Karge, Per Karlsson, Dan Katz, Sol Katzman, Ian +Kennedy, Michael Killough, Sun Kim, Jonathan Kimmitt, Olof Kindgren, Kevin +Kiningham, Dan Kirkham, Aleksander Kiryk, Sobhan Klnv, Gernot Koch, Jack +Koenig, Soon Koh, Nathan Kohagen, Steve Kolecki, Brett Koonce, Will +Korteland, Wojciech Koszek, Varun Koyyalagunta, Arkadiusz Kozdra, Markus +Krause, David Kravitz, Roland Kruse, Andreas Kuster, Sergey Kvachonok, +Charles Eric LaForest, Ed Lander, Steve Lang, Stephane Laurent, Walter +Lavino, Christian Leber, Larry Lee, Yoda Lee, Michaël Lefebvre, Igor Lesik, +John Li, Eivind Liland, Yu Sheng Lin, Charlie Lind, Andrew Ling, Jiuyang +Liu, Paul Liu, Derek Lockhart, Jake Longo, Geza Lore, Arthur Low, Stefan +Ludwig, Dan Lussier, Fred Ma, Duraid Madina, Affe Mao, Julien Margetts, +Mark Marshall, Alfonso Martinez, Unai Martinez-Corral, Adrien Le Masle, +Yves Mathieu, Patrick Maupin, Conor McCullough, Jason McMullan, Elliot +Mednick, David Metz, Wim Michiels, Miodrag Milanovic, Kevin Millis, Wai Sum +Mong, Peter Monsson, Sean Moore, Dennis Muhlestein, John Murphy, Matt +Myers, Nathan Myers, Richard Myers, Dimitris Nalbantis, Peter Nelson, Bob +Newgard, Rachit Nigam, Paul Nitza, Yossi Nivin, Pete Nixon, Lisa Noack, +Mark Nodine, Kuba Ober, Andreas Olofsson, Baltazar Ortiz, Aleksander Osman, +Don Owen, James Pallister, Vassilis Papaefstathiou, Brad Parker, Morten +Borup Petersen, Dan Petrisko, Maciej Piechotka, David Pierce, Cody +Piersall, Michael Platzer, Dominic Plunkett, David Poole, Mike Popoloski, +Roman Popov, Rich Porter, Stefan Post, Niranjan Prabhu, Damien Pretet, Usha +Priyadharshini, Mark Jackson Pulver, Prateek Puri, Marshal Qiao, Nandu Raj, +Kamil Rakoczy, Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh +Redford, Odd Magne Reitan, Frederic Requin, Dustin Richmond, Samuel Riedel, +Alberto Del Rio, Eric Rippey, Oleg Rodionov, Ludwig Rogiers, Paul Rolfe, +Arjen Roodselaar, Tobias Rosenkranz, Ryszard Rozak, Huang Rui, Graham +Rushton, Jan Egil Ruud, Denis Rystsov, John Sanguinetti, Martin Schmidt, +Julie Schwartz, Galen Seitz, Joseph Shaker, Salman Sheikh, Hao Shi, Mike +Shinkarovsky, Rafael Shirakawa, Jeffrey Short, Fan Shupei, Anderson Ignacio +da Silva, Rodney Sinclair, Ameya Vikram Singh, Steven Slatter, Mladen +Slijepcevic, Brian Small, Garrett Smith, Tim Snyder, Wilson Snyder, Maciej +Sobkowski, Stan Sokorac, Alex Solomatnikov, Flavien Solt, Wei Song, Trefor +Southwell, Martin Stadler, Art Stamness, David Stanford, John Stevenson, +Pete Stevenson, Patrick Stewart, Rob Stoddard, Todd Strader, John Stroebel, +Sven Stucki, Howard Su, Emerson Suguimoto, Gene Sullivan, Qingyao Sun, +Renga Sundararajan, Gustav Svensk, Rupert Swarbrick, Yutetsu Takatsukasa, +Thierry Tambe, Drew Taussig, Jose Tejada, Peter Tengstrand, Wesley +Terpstra, Rui Terra, Stefan Thiede, Gary Thomas, Ian Thompson, Kevin +Thompson, Mike Thyer, Hans Tichelaar, Viktor Tomov, Steve Tong, Topa +Topino, Alex Torregrosa, Michael Tresidder, David Turner, Neil Turton, Mike +Urbach, Hans Van Antwerpen, Sebastien Van Cauwenberghe, Laurens van Dam, +Leendert van Doorn, Cong Van Nguyen, Jan Van Winkel, Srini Vemuri, Yuri Victorovich, Bogdan Vukobratovic, Holger Waechtler, Philipp Wagner, Stefan -Wallentowitz, Shawn Wang, Paul Wasson, Greg Waters, Thomas Watts, Eugene -Weber, David Welch, Martin Whitaker, Marco Widmer, Leon Wildman, Daniel -Wilkerson, Gerald Williams, Trevor Williams, Jeff Winston, Joshua Wise, -Clifford Wolf, Tobias Wolfel, Johan Wouters, Paul Wright, Junyi Xi, Ding -Xiaoliang, Jie Xu, Mandy Xu, Yinan Xu, Luke Yang, Amir Yazdanbakhsh, and -Keyi Zhang. +Wallentowitz, Shawn Wang, Zhanglei Wang, Paul Wasson, Greg Waters, Thomas +Watts, Eugene Weber, David Welch, Thomas J Whatson, Martin Whitaker, Marco +Widmer, Leon Wildman, Daniel Wilkerson, Gerald Williams, Trevor Williams, +Jeff Winston, Joshua Wise, Clifford Wolf, Tobias Wolfel, Johan Wouters, +Paul Wright, Junyi Xi, Ding Xiaoliang, Jie Xu, Mandy Xu, Yinan Xu, Luke +Yang, Amir Yazdanbakhsh, Keyi Zhang, and Xi Zhang. Thanks to them, and all those we've missed including above, or wished to remain anonymous. @@ -151,6 +163,10 @@ In 2018, Verilator 4.000 was released with multithreaded support. In 2019, Verilator joined the `CHIPS Alliance `_. +In 2022, Verilator 5.000 was released with IEEE scheduling semantics, +fork/join, delay handling, DFG performance optimizations, and other +improvements. + Currently, various language features and performance enhancements are added -as the need arises. Verilator is now about 3x faster than in 2002, and is -faster than most (if not every) other simulator. +as the need arises, with a focus towards getting to full Universal +Verification Methodology (UVM, IEEE 1800.2-2017) support. diff --git a/docs/guide/deprecations.rst b/docs/guide/deprecations.rst index 8c0038453..3214ef8ab 100644 --- a/docs/guide/deprecations.rst +++ b/docs/guide/deprecations.rst @@ -7,9 +7,15 @@ Deprecations The following deprecated items are scheduled for future removal: C++11 compiler support - Verilator currently requires C++11 or newer compilers. Verilator will - require C++14 or newer compilers for both compiling Verilator and - compiling Verilated models no sooner than January 2023. + Verilator currently requires a C++20 or newer compiler for timing, and a + C++11 or newer compiler for no-timing. + + Verilator will require C++14 or newer compilers for both compiling + Verilator and compiling Verilated models with --no-timing no sooner than + January 2023. + + Verilator will require C++20 or newer compilers for both compiling + Verilator and compiling all Verilated models no sooner than January 2025. Verilated_heavy.h The legacy "verilated_heavy.h" include was replaced with just including diff --git a/docs/guide/example_binary.rst b/docs/guide/example_binary.rst new file mode 100644 index 000000000..0cb1514be --- /dev/null +++ b/docs/guide/example_binary.rst @@ -0,0 +1,63 @@ +.. Copyright 2003-2022 by Wilson Snyder. +.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +.. _Example Create-Binary Execution: + +Example Create-Binary Execution +=============================== + +We'll compile this SystemVerilog example into a Verilated simulation binary. For +an example that discusses the next level of detail see :ref:`Example C++ +Execution`. + +.. include:: example_common_install.rst + +Now, let's create an example Verilog file: + +.. code-block:: bash + + mkdir test_our + cd test_our + + cat >our.v <<'EOF' + module our; + initial begin $display("Hello World"); $finish; end + endmodule + EOF + +Now we run Verilator on our little example. + +.. code-block:: bash + + verilator --binary -j 0 -Wall our.v + +Breaking this command down: + +#. :vlopt:`--binary` telling Verilator to do everything needed to create a + simulation executable. + +#. :vlopt:`-j` `0` to Verilate using use as many CPU threads as the machine + has. + +#. :vlopt:`-Wall` so Verilator has stronger lint warnings + enabled. + +#. An finally, :command:`our.v` which is our SystemVerilog design file. + +And now we run it: + +.. code-block:: bash + + obj_dir/Vour + +And we get as output: + +.. code-block:: bash + + Hello World + - our.v:2: Verilog $finish + +Really, you're better off using a Makefile to run the steps for you so when +your source changes it will automatically run all of the appropriate steps. +To aid this Verilator can create a makefile dependency file. For examples +that do this see the :file:`examples` directory in the distribution. diff --git a/docs/guide/example_cc.rst b/docs/guide/example_cc.rst index f5d55c8da..192704559 100644 --- a/docs/guide/example_cc.rst +++ b/docs/guide/example_cc.rst @@ -43,13 +43,10 @@ Now we run Verilator on our little example. .. code-block:: bash - verilator -Wall --cc --exe --build sim_main.cpp our.v + verilator --cc --exe --build -j 0 -Wall sim_main.cpp our.v Breaking this command down: -#. :vlopt:`-Wall` so Verilator has stronger lint warnings - enabled. - #. :vlopt:`--cc` to get C++ output (versus e.g. SystemC or only linting). @@ -61,6 +58,12 @@ Breaking this command down: own compile rules, and run make yourself as we show in :ref:`Example SystemC Execution`.) +#. :vlopt:`-j` `0' to Verilate using use as many CPU threads as the machine + has. + +#. :vlopt:`-Wall` so Verilator has stronger lint warnings + enabled. + #. An finally, :command:`our.v` which is our SystemVerilog design file. Once Verilator completes we can see the generated C++ code under the diff --git a/docs/guide/example_dist.rst b/docs/guide/example_dist.rst index 5b45ed1eb..6e6947071 100644 --- a/docs/guide/example_dist.rst +++ b/docs/guide/example_dist.rst @@ -10,6 +10,8 @@ See the ``examples/`` directory that is part of the distribution, and is installed (in a OS-specific place, often in e.g. ``/usr/local/share/verilator/examples``). These examples include: +examples/make_hello_binary + Example GNU-make simple Verilog->binary conversion examples/make_hello_c Example GNU-make simple Verilog->C++ conversion examples/make_hello_sc diff --git a/docs/guide/example_sc.rst b/docs/guide/example_sc.rst index ef8eb03ca..987554f81 100644 --- a/docs/guide/example_sc.rst +++ b/docs/guide/example_sc.rst @@ -43,7 +43,7 @@ Now we run Verilator on our little example: .. code-block:: bash - verilator -Wall --sc --exe sc_main.cpp our.v + verilator --sc --exe -Wall sc_main.cpp our.v This example does not use --build, therefore we need to explicitly compile it: diff --git a/docs/guide/examples.rst b/docs/guide/examples.rst index d57a82d57..b92fe9a78 100644 --- a/docs/guide/examples.rst +++ b/docs/guide/examples.rst @@ -17,6 +17,7 @@ This section covers the following examples: :maxdepth: 1 :hidden: + example_binary.rst example_cc.rst example_sc.rst example_dist.rst diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index 364ac5fba..03dd1d76e 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -72,7 +72,7 @@ Summary: .. option:: +verilator+prof+threads+window+ - Deprecated. Alias for :vlopt:`+verilator+prof+exec+window+\` + Deprecated. Alias for :vlopt:`+verilator+prof+exec+window+\` .. option:: +verilator+prof+vlt+file+ diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index a54510df4..bae014be9 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -115,6 +115,13 @@ Summary: Using this argument will likely cause incorrect simulation. +.. option:: --binary + + Create a Verilated simulator binary. Alias for :vlopt:`--main` + :vlopt:`--exe` :vlopt:`--build` :vlopt:`--timing`. + + See also :vlopt:`-j`. + .. option:: --build After generating the SystemC/C++ code, Verilator will invoke the @@ -169,10 +176,7 @@ Summary: .. option:: --clk - With :vlopt:`--clk`, the specified signal-name is taken as a root clock - into the model; Verilator will mark the signal as clocker and - propagate the clocker attribute automatically to other signals downstream in - that clock tree. + With :vlopt:`--clk`, the specified signal is marked as a clock signal. The provided signal-name is specified using a RTL hierarchy path. For example, v.foo.bar. If the signal is the input to top-module, then @@ -184,11 +188,11 @@ Summary: individual bits, Verilator will attempt to decompose the vector and connect the single-bit clock signals. - The clocker attribute is useful in cases where Verilator does not - properly distinguish clock signals from other data signals. Using - clocker will cause the signal indicated to be considered a clock, and - remove it from the combinatorial logic reevaluation checking code. This - may greatly improve performance. + In versions prior to 5.000, the clocker attribute is useful in cases where + Verilator does not properly distinguish clock signals from other data + signals. Using clocker will cause the signal indicated to be considered a + clock, and remove it from the combinatorial logic reevaluation checking + code. This may greatly improve performance. .. option:: --no-clk @@ -358,6 +362,11 @@ Summary: touch foo.v ; verilator -E --dump-defines foo.v +.. option:: --dump-dfg + + Rarely needed. Enable dumping DfgGraph .dot debug files with dumping + level 3. + .. option:: --dump-graph Rarely needed. Enable dumping V3Graph .dot debug files with dumping @@ -373,6 +382,12 @@ Summary: <--dump-tree>` may be useful if the dump files are large and not desired. +.. option:: --dump-tree-dot + + Rarely needed. Enable dumping Ast .tree.dot debug files in Graphviz + Dot format. This option implies :vlopt:`--dump-tree`, unless + :vlopt:`--dumpi-tree` was passed explicitly. + .. option:: --dump-tree-addrids Rarely needed - for developer use. Replace AST node addresses with @@ -387,6 +402,11 @@ Summary: Rarely needed - for developer use. Enable all dumping in the given source file at level 3. +.. option:: --dumpi-dfg + + Rarely needed - for developer use. Set internal DfgGraph dumping level + globally to the specified value. + .. option:: --dumpi-graph Rarely needed - for developer use. Set internal V3Graph dumping level @@ -401,8 +421,8 @@ Summary: Rarely needed - for developer use. Set the dumping level in the specified Verilator source file to the specified value (e.g. - :vlopt:`--dumpi-V3Order 9`). Level 0 disables dumps and is equivalent - to :vlopt:`--no-dump-`. Level 9 enables dumping of everything. + `--dumpi-V3Order 9`). Level 0 disables dumps and is equivalent to + `--no-dump-`. Level 9 enables dumping of everything. .. option:: -E @@ -481,10 +501,37 @@ Summary: .. option:: -fno-const +.. options: -fno-const-before-dfg + + Do not apply any global expression folding prior to the DFG pass. This + option is solely for the purpose of DFG testing and should not be used + otherwise. + .. option:: -fno-const-bit-op-tree .. option:: -fno-dedup +.. option:: -fno-dfg + + Disable all use of the DFG based combinational logic optimizer. + Alias for :vlopt:`-fno-dfg-pre-inline` and :vlopt:`-fno-dfg-post-inline`. + +.. option:: -fno-dfg-peephole + + Disable the DFG peephole optimizer. + +.. option:: -fno-dfg-peephole- + + Disable individual DFG peephole optimizer pattern. + +.. option:: -fno-dfg-pre-inline + + Do not apply the DFG optimizer before inlining. + +.. option:: -fno-dfg-post-inline + + Do not apply the DFG optimizer after inlining. + .. option:: -fno-expand .. option:: -fno-gate @@ -601,6 +648,15 @@ Summary: a newline and exit immediately. This can be useful in makefiles. See also :vlopt:`-V`, and the various :file:`*.mk` files. +.. option:: --get-supported + + If the given feature is supported, print "1" and exit + immediately. Otherwise, print a newline and exit immediately. This can + be useful in makefiles. See also :vlopt:`-V`, and the various + :file:`*.mk` files. + + Feature may be one of the following: COROUTINES, SYSTEMC. + .. option:: --help Displays this message and program version and exits. @@ -694,6 +750,8 @@ Summary: model has a time resolution that is always compatible with the time precision of the upper instantiating module. + Designs compiled using this option cannot use :vlopt:`--timing` with delays. + See also :vlopt:`--protect-lib`. .. option:: +libext+[+][...] @@ -740,11 +798,20 @@ Summary: Generates a top-level C++ main() file that supports parsing arguments, but does not drive any inputs. This is sufficient to use for top-level - SystemVerilog designs that has no inputs, and does not need the C++ to - do any time advancement. + SystemVerilog designs that has no inputs. + + This option can also be used once to generate a main .cpp file as a + starting point for editing. Copy it outside the obj directory, manually + edit, and then pass the filename on later Verilator command line + invocations. + + Typically used with :vlopt:`--timing` to support delay-generated clocks, + and :vlopt:`--build`. Implies :vlopt:`--cc` if no other output mode was provided. + See also :vlopt:`--binary`. + .. option:: --max-num-width Set the maximum number literal width (e.g. in 1024'd22 this it the @@ -806,6 +873,10 @@ Summary: .. option:: --no-order-clock-delay + Deprecated and has no effect (ignored). + + In versions prior to 5.000: + Rarely needed. Disables a bug fix for ordering of clock enables with delayed assignments. This option should only be used when suggested by the developers. @@ -1008,6 +1079,8 @@ Summary: in the distribution for a demonstration of how to build and use the DPI library. + Designs compiled using this option cannot use :vlopt:`--timing` with delays. + .. option:: --public This is only for historical debug use. Using it may result in @@ -1199,6 +1272,16 @@ Summary: module. As "1fs" is the finest time precision it may be desirable to always use a precision of "1fs". +.. option:: --timing + +.. option:: --no-timing + + Enables/disables support for timing constructs such as delays, event + controls (unless it's at the top of a process), wait statements, and joins. + When disabled, timing control constructs are ignored the same way as they + were in earlier versions of Verilator. Enabling this feature requires a C++ + compiler with coroutine support (GCC 10, Clang 5, or newer). + .. option:: --top .. option:: --top-module @@ -1362,8 +1445,7 @@ Summary: Enable all code style warnings, including code style warnings that are normally disabled by default. Equivalent to :vlopt:`-Wwarn-lint` - :vlopt:`-Wwarn-style`. Excludes some specialty warnings, - i.e. IMPERFECTSCH. + :vlopt:`-Wwarn-style`. Excludes some specialty warnings. .. option:: -Werror- @@ -1406,7 +1488,8 @@ Summary: equivalent to ``-Wno-ALWCOMBORDER -Wno-BSSPACE -Wno-CASEINCOMPLETE -Wno-CASEOVERLAP -Wno-CASEX -Wno-CASTCONST -Wno-CASEWITHX -Wno-CMPCONST -Wno-COLONPLUS -Wno-ENDLABEL -Wno-IMPLICIT -Wno-LITENDIAN -Wno-PINCONNECTEMPTY - -Wno-PINMISSING -Wno-SYNCASYNCNET -Wno-UNDRIVEN -Wno-UNSIGNED -Wno-UNUSED + -Wno-PINMISSING -Wno-SYNCASYNCNET -Wno-UNDRIVEN -Wno-UNSIGNED + -Wno-UNUSEDGENVAR -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL -Wno-WIDTH`` plus the list shown for Wno-style. It is strongly recommended you cleanup your code rather than using this @@ -1418,7 +1501,8 @@ Summary: Disable all code style related warning messages (note by default they are already disabled). This is equivalent to ``-Wno-DECLFILENAME -Wno-DEFPARAM -Wno-EOFNEWLINE -Wno-IMPORTSTAR -Wno-INCABSPATH -Wno-PINCONNECTEMPTY - -Wno-PINNOCONNECT -Wno-SYNCASYNCNET -Wno-UNDRIVEN -Wno-UNUSED + -Wno-PINNOCONNECT -Wno-SYNCASYNCNET -Wno-UNDRIVEN + -Wno-UNUSEDGENVAR -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL -Wno-VARHIDDEN``. .. option:: -Wpedantic @@ -1447,7 +1531,7 @@ Summary: Enable all code style related warning messages. This is equivalent to ``-Wwarn ASSIGNDLY -Wwarn-DECLFILENAME -Wwarn-DEFPARAM -Wwarn-EOFNEWLINE -Wwarn-INCABSPATH -Wwarn-PINNOCONNECT -Wwarn-SYNCASYNCNET -Wwarn-UNDRIVEN - -Wwarn-UNUSED -Wwarn-VARHIDDEN``. + -Wwarn-UNUSEDGENVAR -Wwarn-UNUSEDPARAM -Wwarn-UNUSEDSIGNAL -Wwarn-VARHIDDEN``. .. option:: --x-assign 0 @@ -1616,6 +1700,10 @@ The grammar of configuration commands is as follows: .. option:: clock_enable -module "" -var "" + Deprecated and has no effect (ignored). + + In versions prior to 5.000: + Indicate the signal is used to gate a clock, and the user takes responsibility for insuring there are no races related to it. @@ -1779,6 +1867,19 @@ The grammar of configuration commands is as follows: Same as :option:`/*verilator&32;split_var*/` metacomment. +.. option:: timing_on [-file "" [-lines [ - ]]] + +.. option:: timing_off [-file "" [-lines [ - ]]] + + Enables/disables timing constructs for the specified file and lines. + When disabled, all timing control constructs in the specified source + code locations are ignored the same way as with the + :option:`--no-timing`, and code:`fork`/:code:`join*` blocks are + converted into :code:`begin`/:code:`end` blocks. + + Same as :option:`/*verilator&32;timing_on*/`, + :option:`/*verilator&32;timing_off*/` metacomments. + .. option:: tracing_on [-file "" [-lines [ - ]]] .. option:: tracing_off [-file "" [-lines [ - ]]] diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index bd5171c52..fd65e8b0a 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -151,6 +151,13 @@ or "`ifdef`"'s may break other tools. Take remaining text up to the next :option:`\`verilog` mode switch and treat it as Verilator configuration commands. See :ref:`Configuration Files`. +.. option:: `VERILATOR_TIMING + + The VERILATOR_TIMING define is set when :vlopt:`--timing` is used to + allow an "\`ifdef" of code dependent on this feature. Note this define + is not affected by the :option:`timing_off` configuration file option + nor timing metacomments. + .. option:: `verilog Switch back to processing Verilog code after a @@ -160,6 +167,10 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;clock_enable*/ + Deprecated and has no effect (ignored). + + In versions prior to 5.000: + Used after a signal declaration to indicate the signal is used to gate a clock, and the user takes responsibility for insuring there are no races related to it. (Typically by adding a latch, and running static timing @@ -174,9 +185,7 @@ or "`ifdef`"'s may break other tools. The clock_enable attribute will cause the clock gate to be ignored in the scheduling algorithm, sometimes required for correct clock behavior, - and always improving performance. It's also a good idea to enable the - :option:`IMPERFECTSCH` warning, to ensure all clock enables are properly - recognized. + and always improving performance. Same as :option:`clock_enable` configuration file option. @@ -184,9 +193,7 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;no_clocker*/ - Specifies that the signal is used as clock or not. This information is - used by Verilator to mark the signal and any derived signals as - clocker. See :vlopt:`--clk`. + Specifies that the signal is used as clock or not. See :vlopt:`--clk`. Same as :option:`clocker` and :option:`no_clocker` in configuration files. @@ -503,6 +510,22 @@ or "`ifdef`"'s may break other tools. Verilator) text that should be passed through to the XML output as a tag, for use by downstream applications. +.. option:: /*verilator&32;timing_off*/ + + Ignore all timing constructs after this metacomment. All timing controls + behave as if they were not there (the same way as with + :option:`--no-timing`), and :code:`fork`/:code:`join*` blocks are + converted into :code:`begin`/:code:`end` blocks. + + Same as :option:`timing_off` configuration file option. + +.. option:: /*verilator&32;timing_on*/ + + Re-enable all timing constructs after this metacomment (only applicable + after :option:`timing_off`). + + Same as :option:`timing_on` configuration file option. + .. option:: /*verilator&32;trace_init_task*/ Attached to a DPI import to indicate that function should be called when diff --git a/docs/guide/files.rst b/docs/guide/files.rst index a71214983..2b797c62a 100644 --- a/docs/guide/files.rst +++ b/docs/guide/files.rst @@ -93,7 +93,7 @@ For --cc/--sc, it creates: * - *{prefix}{each_verilog_module}{__n}*\ .cpp - Additional lower C++ files * - *{prefix}{each_verilog_module}{__DepSet_hash__n}*\ .cpp - - Additional lower C++ files (hased to reduce build times) + - Additional lower C++ files (hashed to reduce build times) For --hierarchy mode, it creates: diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 9b8adc596..e600dc314 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -95,6 +95,72 @@ keywords on case statement, as well as "unique" on if statements. However, "priority if" is currently ignored. +Time +==== + +With :vlopt:`--timing`, all timing controls are supported: + +* delay statements, +* event control statements not only at the top of a process, +* intra-assignment timing controls, +* net delays, +* :code:`wait` statements, + +as well as all flavors of :code:`fork`. + +Compiling a verilated design that makes use of these features requires a +compiler with C++20 coroutine support, e.g. Clang 5, GCC 10, or newer. + +:code:`#0` delays cause Verilator to issue the :option:`ZERODLY` warning, as +they work differently than described in the LRM. They do not schedule process +resumption in the Inactive region, though the process will get resumed in the +same time slot. + +Rising/falling/turn-off delays are currently unsupported and cause the +:option:`RISEFALLDLY` warning. + +Minimum/typical/maximum delays are currently unsupported. The typical delay is +always the one chosen. Such expressions cause the :option:`MINTYPMAX` warning. + +Another consequence of using :vlopt:`--timing` is that the :vlopt:`--main` +option generates a main file with a proper timing eval loop, eliminating the +need for writing any driving C++ code. You can then simply compile the +simulation (perhaps using :vlopt:`--build`) and run it. + +With :vlopt:`--no-timing`, all timing controls cause the :option:`NOTIMING` +error, with the exception of: + +* delay statements – they are ignored (as they are in synthesis), though they + do issue a :option:`STMTDLY` warning, +* intra-assignment timing controls – they are ignored, though they do issue an + :option:`ASSIGNDLY` warning, +* net delays – they are ignored, +* event controls at the top of the procedure, + +Forks cause this error as well, with the exception of: + +* forks with no statements, +* :code:`fork..join` or :code:`fork..join_any` with one statement, +* forks with :vlopt:`--bbox-unsup`. + +If neither :vlopt:`--timing` nor :vlopt:`--no-timing` is specified, all timing +controls cause the :option:`NEEDTIMINGOPT` error, with the exception of event +controls at the top of the process. Forks cause this error as well, with the +exception of: + +* forks with no statements, +* :code:`fork..join` or :code:`fork..join_any` with one statement, +* forks with :vlopt:`--bbox-unsup`. + +Timing controls and forks can also be ignored in specific files or parts of +files. The :option:`/*verilator&32;timing_off*/` and +:option:`/*verilator&32;timing_off*/` metacomments will make Verilator ignore +the encompassed timing controls and forks, regardless of the chosen +:vlopt:`--timing` or :vlopt:`--no-timing` option. This can also be achieved +using the :option:`timing_off` and :option:`timing_off` options in Verilator +configuration files. + + .. _Language Limitations: Language Limitations @@ -180,12 +246,6 @@ structure from blocking, and another from non-blocking assignments is unsupported. -Time ----- - -All delays (#) are ignored, as they are in synthesis. - - .. _Unknown States: Unknown States @@ -276,17 +336,6 @@ probably expect, what C does. The default behavior of Verilog is different.) -Generated Clocks ----------------- - -Verilator attempts to deal with generated and gated clocks correctly, -however some cases cause problems in the scheduling algorithm which is -optimized for performance. The safest option is to have all clocks as -primary inputs to the model, or wires directly attached to primary inputs. -For proper behavior clock enables may also need the -:option:`/*verilator&32;clock_enable*/` metacomment. - - Gate Primitives --------------- diff --git a/docs/guide/verilating.rst b/docs/guide/verilating.rst index 2af18c1f0..3921a1964 100644 --- a/docs/guide/verilating.rst +++ b/docs/guide/verilating.rst @@ -113,6 +113,8 @@ Hierarchy blocks have some limitations including: hierarchical model and pass up into another hierarchical model or the top module. +* Delays are not allowed in hierarchy blocks. + But, the following usage is supported: * Nested hierarchy blocks. A hierarchy block may instantiate other diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 55206295c..681f11ab5 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -5,6 +5,7 @@ Errors and Warnings ******************* +.. _Disabling Warnings: Disabling Warnings ================== @@ -111,6 +112,8 @@ List Of Warnings simulators, however at one point this was a common style so disabled by default as a code style warning. + This warning is issued only if Verilator is run with :vlopt:`--no-timing`. + .. option:: ASSIGNIN @@ -315,16 +318,16 @@ List Of Warnings .. option:: CLKDATA - .. TODO better example + Historical, never issued since version 5.000. - Warns that clock signal is mixed used with/as data signal. The checking - for this warning is enabled only if user has explicitly marked some - signal as clocker using command line option or in-source meta comment - (see :vlopt:`--clk`). + Warned that clock signal was mixed used with/as data signal. The + checking for this warning was enabled only if user has explicitly marked + some signal as clocker using command line option or in-source meta + comment (see :vlopt:`--clk`). - The warning can be disabled without affecting the simulation result. But - it is recommended to check the warning as this may degrade the - performance of the Verilated model. + The warning could be disabled without affecting the simulation + result. But it was recommended to check the warning as it may have + degraded the performance of the Verilated model. .. option:: CMPCONST @@ -462,15 +465,11 @@ List Of Warnings .. option:: DETECTARRAY - .. TODO better example + Historical, never issued since version 3.862. - Error when Verilator tries to deal with a combinatorial loop that could - not be flattened, and which involves a datatype which Verilator cannot - handle, such as an unpacked struct or a large unpacked array. This - typically occurs when :vlopt:`-Wno-UNOPTFLAT ` has been used - to override an UNOPTFLAT warning (see below). - - The solution is to break the loop, as described for UNOPTFLAT. + Was an error when Verilator tried to deal with a combinatorial loop that + could not be flattened, and which involves a datatype which Verilator + could not handle, such as an unpacked struct or a large unpacked array. .. option:: DIDNOTCONVERGE @@ -492,7 +491,7 @@ List Of Warnings passing. Thus to prevent an infinite loop, the Verilated executable gives the DIDNOTCONVERGE error. - To debug this, first review any UNOPT or UNOPTFLAT warnings that were + To debug this, first review any UNOPTFLAT warnings that were ignored. Though typically it is safe to ignore UNOPTFLAT (at a performance cost), at the time of issuing a UNOPTFLAT Verilator did not know if the logic would eventually converge and assumed it would. @@ -516,9 +515,10 @@ List Of Warnings constructs (e.g. always_ff and always_latch). Another way DIDNOTCONVERGE may occur is if # delays are used to generate - clocks. Verilator ignores the delays and gives an :option:`ASSIGNDLY` - or :option:`STMTDLY` warning. If these were suppressed, due to the - absence of the delay, the code may now oscillate. + clocks if Verilator is run with :vlopt:`--no-timing`. In this mode, + Verilator ignores the delays and gives an :option:`ASSIGNDLY` or + :option:`STMTDLY` warning. If these were suppressed, due to the absence of + the delay, the code may now oscillate. Finally, rare, more difficult cases can be debugged like a C++ program; either enter :command:`gdb` and use its tracing facilities, or edit the @@ -589,9 +589,10 @@ List Of Warnings .. option:: GENCLK - Deprecated and no longer used as a warning. Used to indicate that the - specified signal was is generated inside the model, and also being used - as a clock. + Historical, never issued since version 5.000. + + Indicated that the specified signal was generated inside the model, and + was also being used as a clock. .. option:: HIERBLOCK @@ -653,12 +654,12 @@ List Of Warnings .. option:: IMPERFECTSCH - .. TODO better example + Historical, never issued since version 5.000. - Warns that the scheduling of the model is not absolutely perfect, and + Warned that the scheduling of the model is not absolutely perfect, and some manual code edits may result in faster performance. This warning - defaults to off, is not part of -Wall, and must be turned on explicitly - before the top module statement is processed. + defaulted to off, was not part of -Wall, and had to be turned on + explicitly before the top module statement is processed. .. option:: IMPLICIT @@ -730,9 +731,9 @@ List Of Warnings Warns that a while or for statement has a condition that is always true. and thus results in an infinite loop if the statement ever executes. - This might be unintended behavior if the loop body contains statements - that in other simulators would make time pass, which Verilator is - ignoring due to e.g. ``STMTDLY`` warnings being disabled. + This might be unintended behavior if Verilator is run with + :vlopt:`--no-timing` and the loop body contains statements that would make + time pass otherwise. Ignoring this warning will only suppress the lint check, it will simulate correctly (i.e. hang due to the infinite loop). @@ -793,6 +794,16 @@ List Of Warnings simulate correctly. +.. option:: MINTYPMAX + + .. code-block:: sv + + #(3:5:8) clk = ~clk; + + Warns that minimum, typical, and maximum delay expressions are currently + unsupported. Only the typical delay value is used by Verilator. + + .. option:: MODDUP .. TODO better example @@ -863,6 +874,13 @@ List Of Warnings input. +.. option:: NEEDTIMINGOPT + + Error when a timing-related construct, such as an event control or delay, + has been encountered, without specifying how Verilator should handle it + (neither :vlopt:`--timing` nor :vlopt:`--no-timing` option was provided). + + .. option:: NOLATCH .. TODO better example @@ -875,6 +893,13 @@ List Of Warnings simulate correctly. +.. option:: NOTIMING + + Error when a timing-related construct that requires :vlopt:`--timing` has + been encountered. Issued only if Verilator is run with the + :vlopt:`--no-timing` option. + + .. option:: NULLPORT Warns that a null port was detected in the module definition port @@ -1112,6 +1137,16 @@ List Of Warnings "Duplicate macro arguments with name". +.. option:: RISEFALLDLY + + .. code-block:: sv + + and #(1,2,3) AND (out, a, b); + + Warns that rising, falling, and turn-off delays are currently unsupported. + The first (rising) delay is used for all cases. + + .. option:: SELRANGE Warns that a selection index will go out of bounds. @@ -1211,11 +1246,11 @@ List Of Warnings .. include:: ../../docs/gen/ex_STMTDLY_msg.rst - This is a warning because Verilator does not support delayed statements. - It will ignore all such delays. In many cases ignoring a delay might be - harmless, but if the delayed statement is, as in this example, used to - cause some important action at a later time, it might be an important - difference. + This warning is issued only if Verilator is run with :vlopt:`--no-timing`. + All delays on statements are ignored in this mode. In many cases ignoring a + delay might be harmless, but if the delayed statement is, as in this + example, used to cause some important action at a later time, it might be an + important difference. Some possible workarounds: @@ -1225,6 +1260,8 @@ List Of Warnings * Convert the statement into a FSM, or other statement that tests against $time. + * Run Verilator with :vlopt:`--timing`. + .. option:: SYMRSVDWORD @@ -1355,23 +1392,12 @@ List Of Warnings .. option:: UNOPT - .. TODO better example + Historical, never issued since version 5.000. - Warns that due to some construct, optimization of the specified signal - or block is disabled. The construct should be cleaned up to improve - simulation performance. + Warned that due to some construct, optimization of the specified signal + or block was disabled. - A less obvious case of this is when a module instantiates two - submodules. Inside submodule A, signal I is input and signal O is - output. Likewise in submodule B, signal O is an input and I is an - output. A loop exists and a UNOPT warning will result if AI & AO both - come from and go to combinatorial blocks in both submodules, even if - they are unrelated always blocks. This affects performance because - Verilator would have to evaluate each submodule multiple times to - stabilize the signals crossing between the modules. - - Ignoring this warning will only slow simulations, it will simulate - correctly. + Ignoring this warning only slowed simulations, it simulated correctly. .. option:: UNOPTFLAT @@ -1385,10 +1411,6 @@ List Of Warnings performance; two times better performance may be possible by fixing these warnings. - Unlike the ``UNOPT`` warning, this occurs after flattening the netlist, - and indicates a more basic problem, as the less obvious case described - under ``UNOPT`` does not apply. - Often UNOPTFLAT is caused by logic that isn't truly circular as viewed by synthesis which analyzes interconnection per-bit, but is circular to simulation which analyzes per-bus. @@ -1441,15 +1463,15 @@ List Of Warnings the conflict. If you run with `--report-unoptflat` Verilator will suggest possible candidates for :option:`/*verilator&32;split_var*/`. - The UNOPTFLAT warning may also be due to clock enables, identified from - the reported path going through a clock gating instance. To fix these, - use the clock_enable meta comment described above. - The UNOPTFLAT warning may also occur where outputs from a block of logic are independent, but occur in the same always block. To fix this, use the :option:`/*verilator&32;isolate_assignments*/` metacomment described above. + Prior to version 5.000, the UNOPTFLAT warning may also have been due to + clock enables, identified from the reported path going through a clock + gating instance. To fix these, the clock_enable meta comment was used. + To assist in resolving UNOPTFLAT, the option :vlopt:`--report-unoptflat` can be used, which will provide suggestions for variables that can be split up, and a graph of all the nodes connected in the loop. See the @@ -1520,9 +1542,31 @@ List Of Warnings .. option:: UNUSED + Disabling/enabling UNUSED is equivalent to disabling/enabling the + `UNUSEDGENVAR`, `UNUSEDPARAM` and `UNUSEDSIGNAL` warnings. + + Never issued since version 5.000. Historically warned that a variable, + parameter, or signal was unused. + +.. option:: UNUSEDGENVAR + .. TODO better example - Warns that the specified signal or parameter is never used/consumed. + Warns that the specified genvar is never used/consumed. + + +.. option:: UNUSEDPARAM + + .. TODO better example + + Warns that the specified parameter is never used/consumed. + + +.. option:: UNUSEDSIGNAL + + .. TODO better example + + Warns that the specified signal is never used/consumed. Verilator is fairly liberal in the usage calculations; making a signal public, a signal matching the :vlopt:`--unused-regexp` option (default "\*unused\*" or accessing only a single array element marks the entire @@ -1635,6 +1679,16 @@ List Of Warnings To resolve, rename the variable to a unique name. +.. option:: WAITCONST + + .. code-block:: sv + + wait(0); // Blocks forever + + Warns that a `wait` statement awaits a constant condition, which means it + either blocks forever or never blocks. + + .. option:: WIDTH Warns that based on width rules of Verilog: @@ -1702,3 +1756,11 @@ List Of Warnings The correct fix is to either size the 1 (:code:`32'h1`), or add the width to the parameter definition (:code:`parameter [31:0]`), or add the width to the parameter usage (:code:`{PAR[31:0], PAR[31:0]}`). + + +.. option:: ZERODLY + + Warns that `#0` delays do not schedule the process to be resumed in the + Inactive region. Such processes do get resumed in the same time slot + somewhere in the Active region. Issued only if Verilator is run with the + :vlopt:`--timing` option. diff --git a/docs/internals.rst b/docs/internals.rst index c62881026..a3f70ea36 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -184,6 +184,568 @@ A number of predefined derived algorithm classes and access methods are provided and documented in ``V3GraphAlg.cpp``. +``DfgGraph`` +^^^^^^^^^^^^^ + +The data-flow graph based combinational logic optimizer (DFG optimizer) +converts an ``AstModule`` into a ``DfgGraph``. The graph represents the +combinational equations (~continuous assignments) in the module, and for the +duration of the DFG passes, it takes over the role of the represented +``AstModule``. The ``DfgGraph`` keeps holds of the represented ``AstModule``, +and the ``AstModule`` retains all other logic that is not representable as a +data-flow graph. At the end of optimization, the combinational logic +represented by the ``DfgGraph`` is converted back into AST form and is +re-inserted into the corresponding ``AstModule``. The ``DfgGraph`` is distinct +from ``V3Graph`` for efficiency and other desirable properties which make +writing DFG passes easier. + + +``DfgVertex`` +^^^^^^^^^^^^^ + +The ``DfgGraph`` represents combinational logic equations as a graph of +``DfgVertex`` vertices. Each sub-class of ``DfgVertex`` corresponds to an +expression (a sub-class of ``AstNodeMath``), a constanat, or a variable +reference. LValues and RValues referencing the same storage location are +represented by the same ``DfgVertex``. Consumers of such vertices read as the +LValue, writers of such vertices write the RValue. The bulk of the final +``DfgVertex`` sub-classes are generated by ``astgen`` from the corresponding +``AstNode`` definitions. + + +Scheduling +---------- + +Verilator implements the Active and NBA regions of the SystemVerilog scheduling +model as described in IEEE 1800-2017 chapter 4, and in particular sections +4.5 and Figure 4.1. The static (verilation time) scheduling of SystemVerilog +processes is performed by code in the ``V3Sched`` namespace. The single +entry-point to the scheduling algorithm is ``V3Sched::schedule``. Some +preparatory transformations important for scheduling are also performed in +``V3Active`` and ``V3ActiveTop``. High level evaluation functions are +constructed by ``V3Order``, which ``V3Sched`` invokes on subsets of the logic +in the design. + +Scheduling deals with the problem of evaluating 'logic' in the correct order +and the correct number of times in order to compute the correct state of the +SystemVerilog program. Throughout this section, we use the term 'logic' to +refer to all SystemVerilog constructs that describe the evolution of the state +of the program. In particular, all SystemVerilog processes and continuous +assignments are considered 'logic', but not for example variable definitions +without initialization or other miscellaneous constructs. + + +Classes of logic +^^^^^^^^^^^^^^^^ + +The first step in the scheduling algorithm is to gather all the logic present +in the design, and classify it based on the conditions under which the logic +needs to be evaluated. + +The classes of logic we distinguish between are: + +- SystemVerilog ``initial`` processes, that need to be executed once at + startup. + +- Static variable initializers. These are a separate class as they need to be + executed before ``initial`` processes. + +- SystemVerilog ``final`` processes. + +- Combinational logic. Any process or construct that has an implicit + sensitivity list with no explicit sensitivities is considered 'combinational' + logic. This includes among other things, ``always @*`` and ``always_comb`` + processes, and continuous assignments. Verilator also converts some other + ``always`` processes to combinational logic in ``V3Active`` as described + below. + +- Clocked logic. Any process or construct that has an explicit sensitivity + list, with no implicit sensitivities is considered 'clocked' (or + 'sequential') logic. This includes among other things ``always`` and + ``always_ff`` processes with an explicit sensitivity list. + +Note that the distinction between clocked logic and combinational logic is only +important for the scheduling algorithm within Verilator as we handle the two +classes differently. It is possible to convert clocked logic into combinational +logic if the explicit sensitivity list of the clocked logic is the same as the +implicit sensitivity list of the equivalent combinational logic would be. The +canonical examples are: ``always @(a) x = a;``, which is considered to be +clocked logic by Verilator, and the equivalent ``assign x = a;``, which is +considered to be combinational logic. ``V3Active`` in fact converts all clocked +logic to combinational logic whenever possible, as this provides advantages for +scheduling as described below. + +There is also a 'hybrid' logic class, which has both explicit and implicit +sensitivities. This kind of logic does not arise from a SystemVerilog +construct, but is created during scheduling to break combinational cycles. +Details of this process and the hybrid logic class are described below. + + +Scheduling of simple classes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +SystemVerilog ``initial`` and ``final`` blocks can be scheduled (executed) in an +arbitrary order. + +Static variable initializers need to be executed in source code order in case +there is a dependency between initializers, but the ordering of static variable +initialization is otherwise not defined by the SystemVerilog standard +(particularly, in the presence of hierarchical references in static variable +initializers). + +The scheduling algorithm handles all three of these classes the same way and +schedules the logic in these classes in source code order. This step yields the +``_eval_static``, ``_eval_initial`` and ``_eval_final`` functions which execute +the corresponding logic constructs. + + +Scheduling of clocked and combinational logic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For performance, clocked and combinational logic needs to be ordered. +Conceptually this minimizes the iterations through the evaluation loop +presented in the reference algorithm in the SystemVerilog standard (IEEE +1800-2017 section 4.5), by evaluating logic constructs in data-flow order. +Without going into a lot of detail here, accept that well thought out ordering +is crucial to good simulation performance, and also enables further +optimizations later on. + +At the highest level, ordering is performed by ``V3Order::order``, which is +invoked by ``V3Sched::schedule`` on various subsets of the combinational and +clocked logic as described below. The important thing to highlight now is that +``V3Order::order`` operates by assuming that the state of all variables driven +by combinational logic are consistent with that combinational logic. While this +might seem subtle, it is very important, so here is an example: + +:: + always_comb d = q + 2; + always @(posedge clock) q <= d; + + +During ordering, ``V3Order`` will assume that ``d`` equals ``q + 2`` at the +beginning of an evaluation step. As a result it will order the clocked logic +first, and all downstream combinational logic (like the assignment to ``d``) +will execute after the clocked logic that drives inputs to the combinational +logic, in data-flow (or dependency) order. At the end of the evaluation step, +this ordering restores the invariant that variables driven by combinational +logic are consistent with that combinational logic (i.e.: the circuit is in a +settled/steady state). + +One of the most important optimizations for performance is to only evaluate +combinational logic, if its inputs might have changed. For example, there is no +point in evaluating the above assignment to ``d`` on a negative edge of the +clock signal. Verilator does this by pushing the combinational logic into the +same (possibly multiple) event domains as the logic driving the inputs to that +combinational logic, and only evaluating the combinational logic if at least +one driving domains have been triggered. The impact of this activity gating is +very high (observed 100x slowdown on large designs when turning it off), it is +the reason we prefer to convert clocked logic to combinational logic in +``V3Active`` whenever possible. + +The ordering procedure described above works straight forward unless there are +combinational logic constructs that are circularly dependent (a.k.a.: the +UNOPTFLAT warning). Combinational scheduling loops can arise in sound +(realizable) circuits as Verilator considers each SystemVerilog process as a +unit of scheduling (albeit we do try to split processes into smaller ones to +avoid this circularity problem whenever possible, this is not always possible). + + +Breaking combinational loops +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Combinational loops are broken by the introduction of instances of the 'hybrid' +logic class. As described in the previous section, combinational loops require +iteration until the logic is settled, in order to restore the invariant that +combinationally driven signals are consistent with the combinational logic. + +To achieve this, ``V3Sched::schedule`` calls ``V3Sched::breakCycles``, which +builds a dependency graph of all combinational logic in the design, and then +breaks all combinational cycles by converting all combinational logic that +consumes a variable driven via a 'back-edge' into hybrid logic. Here +'back-edge' just means a graph edge that points from a higher rank vertex to a +lower rank vertex in some consistent ranking of the directed graph. Variables +driven via a back-edge in the dependency graph are marked, and all +combinational logic that depends on such variables is converted into hybrid +logic, with the back-edge driven variables listed as explicit 'changed' +sensitivities. + +Hybrid logic is handled by ``V3Order`` mostly in the same way as combinational +logic, with two exceptions: + +- Explicit sensitivities of hybrid logic are ignored for the purposes of + data-flow ordering with respect to other combinational or hybrid logic. I.e.: + an explicit sensitivity suppresses the implicit sensitivity on the same + variable. This cold also be interpreted as ordering the hybrid logic as if + all variables listed as explicit sensitivities were substituted as constants + with their current values. + +- The explicit sensitivities are included as an additional driving domain of + the logic, and also cause evaluation when triggered. + +This means that hybrid logic is evaluated when either any of its implicit +sensitivities might have been updated (the same way as combinational logic, by +pushing it into the domains that write those variables), or if any of its +explicit sensitivities are triggered. + +The effect of this transformation is that ``V3Order`` can proceed as if there +are no combinational cycles (or alternatively, under the assumption that the +back-edge driven variables don't change during one evaluation pass). The +evaluation loop invoking the ordered code, will then re-invoke it on a follow +on iteration, if any of the explicit sensitivities of hybrid logic have +actually changed due to the previous invocation, iterating until all the +combinational (including hybrid) logic have settled. + +One might wonder if there can be a race condition between clocked logic +triggered due to a combinational signal change from the previous evaluation +pass, and a combinational loop settling due to hybrid logic, if the clocked +logic reads the not yet settled combinationally driven signal. Such a race is +indeed possible, but our evaluation is consistent with the SystemVerilog +scheduling semantics (IEEE 1800-2017 chapter 4), and therefore any program that +exhibits such a race has non-deterministic behaviour according to the +SystemVerilog semantics, so we accept this. + + +Settling combinational logic after initialization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +At the beginning of simulation, once static initializer and ``initial`` blocks +have been executed, we need to evaluate all combinational logic, in order to +restore the invariant utilized by ``V3Order`` that the state of all +combinationally driven variables are consistent with the combinational logic. + +To achieve this, we invoke ``V3Order::order`` on all of the combinational and +hybrid logic, and iterate the resulting evaluation function until no more +hybrid logic is triggered. This yields the `_eval_settle` function which is +invoked at the beginning of simulation, after the `_eval_initial`. + + +Partitioning logic for correct NBA updates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``V3Order`` can order logic corresponding to non-blocking assignments (NBAs) to +yield correct simulation results, as long as all the sensitivity expressions of +clocked logic triggered in the Active scheduling region of the current time +step are known up front. I.e.: the ordering of NBA updates is only correct if +derived clocks that are computed in an Active region update (that is, via a +blocking or continuous assignment) are known up front. + +We can ensure this by partitioning the logic into two regions. Note these +regions are a concept of the Verilator scheduling algorithm and they do not +directly correspond to the similarly named SystemVerilog scheduling regions +as defined in the standard: + +- All logic (clocked, combinational and hybrid) that transitively feeds into, + or drives, via a non-blocking or continuous assignments (or via any update + that SystemVerilog executes in the Active scheduling region), a variable that + is used in the explicit sensitivity list of some clocked or hybrid logic, is + assigned to the 'act' region. + +- All other logic is assigned to the 'nba' region. + +For completeness, note that a subset of the 'act' region logic, specifically, +the logic related to the pre-assignments of NBA updates (i.e.: AstAssignPre +nodes), is handled separately, but is executed as part of the 'act' region. + +Also note that all logic representing the committing of an NBA (i.e.: Ast*Post) +nodes) will be in the 'nba' region. This means that the evaluation of the 'act' +region logic will not commit any NBA updates. As a result, the 'act' region +logic can be iterated to compute all derived clock signals up front. + +The correspondence between the SystemVerilog Active and NBA scheduling regions, +and the internal 'act' and 'nba' regions, is that 'act' contains all Active +region logic that can compute a clock signal, while 'nba' contains all other +Active and NBA region logic. For example, if the only clocks in the design are +top level inputs, then 'act' will be empty, and 'nba' will contain the whole of +the design. + +The partitioning described above is performed by ``V3Sched::partition``. + + +Replication of combinational logic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We will separately invoke ``V3Order::order`` on the 'act' and 'nba' region +logic. + +Combinational logic that reads variables driven from both 'act' and 'nba' +region logic has the problem of needing to be re-evaluated even if only one of +the regions updates an input variable. We could pass additional trigger +expressions between the regions to make sure combinational logic is always +re-evaluated, or we can replicate combinational logic that is driven from +multiple regions, by copying it into each region that drives it. Experiments +show this simple replication works well performance-wise (and notably +``V3Combine`` is good at combining the replicated code), so this is what we do +in ``V3Sched::replicateLogic``. + +In ``V3Sched::replicateLogic``, in addition to replicating logic into the 'act' +and 'nba' regions, we also replicate combinational (and hybrid) logic that +depends on top level inputs. These become a separate 'ico' region (Input +Combinational logic), which we will always evaluate at the beginning of a +time-step to ensure the combinational invariant holds even if input signals +have changed. Note that this eliminates the need of changing data and clock +signals on separate evaluations, as was necessary with earlier versions of +Verilator). + + +Constructing the top level `_eval` function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To construct the top level `_eval` function, which updates the state of the +circuit to the end of the current time step, we invoke ``V3Order::order`` +separately on the 'ico', 'act' and 'nba' logic, which yields the `_eval_ico`, +`_eval_act`, and `_eval_nba` functions. We then put these all together with the +corresponding functions that compute the respective trigger expressions into +the top level `_eval` function, which on the high level has the form: + +:: + void _eval() { + // Update combinational logic dependent on top level inptus ('ico' region) + while (true) { + _eval__triggers__ico(); + // If no 'ico' region trigger is active + if (!ico_triggers.any()) break; + _eval_ico(); + } + + + // Iterate 'act' and 'nba' regions together + while (true) { + + // Iterate 'act' region, this computes all derived clocks updaed in the + // Active scheduling region, but does not commit any NBAs that executed + // in 'act' region logic. + while (true) { + _eval__triggers__act(); + // If no 'act' region trigger is active + if (!act_triggers.any()) break; + // Remember what 'act' triggers were active, 'nba' uses the same + latch_act_triggers_for_nba(); + _eval_act(); + } + + + // If no 'nba' region trigger is active + if (!nba_triggers.any()) break; + + // Evaluate all other Active region logic, and commti NBAs + _eval_nba(); + } + } + + +Timing +------ + +Timing support in Verilator utilizes C++ coroutines, which is a new feature in +C++20. The basic idea is to represent processes and tasks that await a certain +event or simulation time as coroutines. These coroutines get suspended at the +await, and resumed whenever the triggering event occurs, or at the expected +simulation time. + +There are several runtime classes used for managing such coroutines defined in +``verilated_timing.h`` and ``verilated_timing.cpp``. + +``VlCoroutineHandle`` +^^^^^^^^^^^^^^^^^^^^^ + +A thin wrapper around an ``std::coroutine_handle<>``. It forces move semantics, +destroys the coroutine if it remains suspended at the end of the design's +lifetime, and prevents multiple ``resume`` calls in the case of +``fork..join_any``. + +``VlCoroutine`` +^^^^^^^^^^^^^^^ + +Return value of all coroutines. Together with the promise type contained +within, it allows for chaining coroutines – resuming coroutines from up the +call stack. The calling coroutine's handle is saved in the promise object as a +continuation, that is, the coroutine that must be resumed after the promise's +coroutine finishes. This is necessary as C++ coroutines are stackless, meaning +each one is suspended independently of others in the call graph. + +``VlDelayScheduler`` +^^^^^^^^^^^^^^^^^^^^ + +This class manages processes suspended by delays. There is one instance of this +class per design. Coroutines ``co_await`` this object's ``delay`` function. +Internally, they are stored in a heap structure sorted by simulation time in +ascending order. When ``resume`` is called on the delay scheduler, all +coroutines awaiting the current simulation time are resumed. The current +simulation time is retrieved from a ``VerilatedContext`` object. + +``VlTriggerScheduler`` +^^^^^^^^^^^^^^^^^^^^^^ + +This class manages processes that await events (triggers). There is one such +object per each trigger awaited by coroutines. Coroutines ``co_await`` this +object's ``trigger`` function. They are stored in two stages – `uncommitted` +and `ready`. First, they land in the `uncommitted` stage, and cannot be +resumed. The ``resume`` function resumes all coroutines from the `ready` stage +and moves `uncommitted` coroutines into `ready`. The ``commit`` function only +moves `uncommitted` coroutines into `ready`. + +This split is done to avoid self-triggering and triggering coroutines multiple +times. See the `Scheduling with timing` section for details on how this is +used. + +``VlDynamicTriggerScheduler`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Like ``VlTriggerScheduler``, ``VlDynamicTriggerScheduler`` manages processes +that await triggers. However, it does not rely on triggers evaluated externally +by the 'act' trigger eval function. Instead, it is also responsible for trigger +evaluation. Coroutines that make use of this scheduler must adhere to a certain +procedure: + +:: + __Vtrigger = 0; + + while (!__Vtrigger) { + co_await __VdynSched.evaluation(); +
;
+      __Vtrigger = ;
+      [optionally] co_await __VdynSched.postUpdate();
+      ;
+  }
+  co_await __VdynSched.resumption();
+
+The coroutines get resumed at trigger evaluation time, evaluate their local
+triggers, optionally await the post update step, and if the trigger is set,
+await proper resumption in the 'act' eval step.
+
+``VlForkSync``
+^^^^^^^^^^^^^^
+
+Used for synchronizing ``fork..join`` and ``fork..join_any``. Forking
+coroutines ``co_await`` its ``join`` function, and forked ones call ``done``
+when they're finished. Once the required number of coroutines (set using
+``setCounter``) finish execution, the forking coroutine is resumed.
+
+Awaitable utilities
+^^^^^^^^^^^^^^^^^^^
+
+There are also two small utility awaitable types:
+
+* ``VlNow`` is an awaitable that suspends and immediately resumes coroutines.
+  It is used for forcing a coroutine to be moved onto the heap. See the `Forks`
+  section for more detail.
+* ``VlForever`` is used for blocking a coroutine forever. See the `Timing pass`
+  section for more detail.
+
+Timing pass
+^^^^^^^^^^^
+
+The visitor in ``V3Timing.cpp`` transforms each timing control into a ``co_await``.
+
+* event controls are turned into ``co_await`` on a trigger scheduler's
+  ``trigger`` method. The awaited trigger scheduler is the one corresponding to
+  the sentree referenced by the event control. This sentree is also referenced
+  by the ``AstCAwait`` node, to be used later by the static scheduling code.
+* if an event control waits on a local variable or class member, it uses a
+  local trigger which it evaluates inline. It awaits a dynamic trigger
+  scheduler multiple times: for trigger evaluation, updates, and resumption.
+  The dynamic trigger scheduler is responsible for resuming the coroutine at
+  the correct point of evaluation.
+* delays are turned into ``co_await`` on a delay scheduler's ``delay`` method.
+  The created ``AstCAwait`` nodes also reference a special sentree related to
+  delays, to be used later by the static scheduling code.
+* ``join`` and ``join_any`` are turned into ``co_await`` on a ``VlForkSync``'s
+  ``join`` method. Each forked process gets a ``VlForkSync::done`` call at the
+  end.
+
+Assignments with intra-assignment timing controls are simplified into
+assignments after those timing controls, with the LHS and RHS values evaluated
+before them and stored in temporary variables.
+
+``wait`` statements are transformed into while loops that check the condition
+and then await changes in variables used in the condition. If the condition is
+always false, the ``wait`` statement is replaced by a ``co_await`` on a
+``VlForever``. This is done instead of a return in case the ``wait`` is deep in
+a call stack (otherwise the coroutine's caller would continue execution).
+
+Each sub-statement of a ``fork`` is put in an ``AstBegin`` node for easier
+grouping. In a later step, each of these gets transformed into a new, separate
+function. See the `Forks` section for more detail.
+
+Processes that use awaits are marked as suspendable. Later, during ``V3Sched``,
+they are transformed into coroutines. Functions that use awaits get the return
+type of ``VlCoroutine``. This immediately makes them coroutines. Note that if a
+process calls a function that is a coroutine, the call gets wrapped in an
+await, which means the process itself will be marked as suspendable. A virtual
+function is a coroutine if any of its overriding or overridden functions are
+coroutines. The visitor keeps a dependency graph of functions and processes to
+handle such cases.
+
+Scheduling with timing
+^^^^^^^^^^^^^^^^^^^^^^
+
+Timing features in Verilator are built on top of the static scheduler. Triggers
+are used for determining which delay or trigger schedulers should resume. A
+special trigger is used for the delay scheduler. This trigger is set if there
+are any coroutines awaiting the current simulation time
+(``VlDelayScheduler::awaitingCurrentTime()``).
+
+All triggers used by a suspendable process are mapped to variables written in
+that process. When ordering code using ``V3Order``, these triggers are provided
+as external domains of these variables. This ensures that the necessary
+combinational logic is triggered after a coroutine resumption.
+
+There are two functions for managing timing logic called by ``_eval()``:
+
+* ``_timing_commit()``, which commits all coroutines whose triggers were not set
+  in the current iteration,
+* ``_timing_resume()``, which calls `resume()` on all trigger and delay
+  schedulers whose triggers were set in the current iteration.
+
+Thanks to this separation, a coroutine awaiting a trigger cannot be suspended
+and resumed in the same iteration, and it cannot be resumed before it suspends.
+
+All coroutines are committed and resumed in the 'act' eval loop. With timing
+features enabled, the ``_eval()`` function takes this form:
+
+::
+  void _eval() {
+    while (true) {
+      _eval__triggers__ico();
+      if (!ico_triggers.any()) break;
+      _eval_ico();
+    }
+
+    while (true) {
+      while (true) {
+        _eval__triggers__act();
+
+        // Commit all non-triggered coroutines
+        _timing_commit();
+
+        if (!act_triggers.any()) break;
+        latch_act_triggers_for_nba();
+
+        // Resume all triggered coroutines
+        _timing_resume();
+
+        _eval_act();
+      }
+      if (!nba_triggers.any()) break;
+      _eval_nba();
+    }
+  }
+
+Forks
+^^^^^
+
+After the scheduling step, forks sub-statements are transformed into separate
+functions, and these functions are called in place of the sub-statements. These
+calls must be without ``co_await``, so that suspension of a forked process
+doesn't suspend the forking process.
+
+In forked processes, references to local variables are only allowed in
+``fork..join``, as this is the only case that ensures the lifetime of these
+locals is at least as long as the execution of the forked processes. This is
+where ``VlNow`` is used, to ensure the locals are moved to the heap before they
+are passed by reference to the forked processes.
+
+
 Multithreaded Mode
 ------------------
 
@@ -515,9 +1077,9 @@ Generating ``AstNode`` members
 Some of the member s of ``AstNode`` sub-classes are generated by ``astgen``.
 These are emitted as pre-processor macro definitions, which then need to be
 added to the ``AstNode`` sub-classes they correspond to. Specifically ``class
-AstFoo`` should contain an instance of ``ASTGEN_MEMBERS_Foo;`` at class scope.
-The ``astgen`` script checks and errors if this is not present. The method
-generated depends on whether the class is a concrete final class, or an
+AstFoo`` should contain an instance of ``ASTGEN_MEMBERS_AstFoo;`` at class
+scope.  The ``astgen`` script checks and errors if this is not present. The
+method generated depends on whether the class is a concrete final class, or an
 abstract ``AstNode*`` base-class, and on ``@astgen`` directives present in
 comment sections in the body of the ``AstNode`` sub-class definitions.
 
@@ -563,6 +1125,7 @@ given ````. For list type children, the getter is ````,
 and instead of the setter, there an ``add`` method is generated
 that appends new nodes (or lists of nodes) to the child list.
 
+
 ``alias op`` operand alias directives
 """"""""""""""""""""""""""""""""""""""""
 
@@ -576,6 +1139,13 @@ super-class of the current node.
 Example: ``@astgen alias op1 := condp``
 
 
+Generating ``DfgVertex`` sub-classes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Most of the ``DfgVertex`` sub-classes are generated by ``astgen``, from the
+definitions of the corresponding ``AstNode`` vertices.
+
+
 Additional features of ``astgen``
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -1137,6 +1707,16 @@ Similarly the ``NETLIST`` has a list of modules referred to by its
 ``op1p()`` pointer.
 
 
+.tree.dot Output
+----------------
+
+``*.tree.dot`` files are dumps of the AST Tree in `Graphviz
+`__ dot format. This can be used to
+visualize the AST Tree. The vertices correspond to ``AstNode``
+instances, and the edges represent the pointers (``op1p``,
+ ``op2p``, etc) between the nodes.
+
+
 Debugging with GDB
 ------------------
 
diff --git a/docs/spelling.txt b/docs/spelling.txt
index 0e423ba26..3e1b7e7d3 100644
--- a/docs/spelling.txt
+++ b/docs/spelling.txt
@@ -7,12 +7,14 @@ Ami
 Amir
 Anastasiadis
 Anikin
+Antmicro
 Antonin
 Antwerpen
 Arasanipalai
 Arjen
 Asciidoc
 Ashutosh
+Ast
 Atmel
 Aurelien
 Bagri
@@ -108,8 +110,10 @@ Goessling
 Gonnen
 Goorah
 Gossner
+Graphviz
 Graybeal
 Grobman
+Grulfen
 Gunter
 Guo
 Hao
@@ -131,6 +135,7 @@ Iles
 Inlines
 Inout
 Iru
+Iyer
 Iztok
 Jacko
 Jae
@@ -157,6 +162,7 @@ Karge
 Karlsson
 Katz
 Katzman
+Kelin
 Keren
 Keyi
 Kimmitt
@@ -340,6 +346,7 @@ Verilating
 Verilation
 Verilator
 Verilog
+Vighnesh
 Viktor
 Vm
 Vukobratovic
@@ -376,6 +383,7 @@ agrobman
 ahouska
 al
 ala
+algrobman
 andit
 ar
 architected
@@ -397,6 +405,7 @@ bbox
 benchmarking
 biguint
 biops
+bisonpre
 bitOpTree
 bitOpTree
 bitop
@@ -441,6 +450,7 @@ const
 constexpr
 constpool
 coredump
+coroutine
 countbits
 countones
 cout
@@ -463,6 +473,7 @@ defenv
 defname
 defparam
 demangling
+dep
 der
 dereference
 desassign
@@ -480,6 +491,7 @@ dsvf
 dtor
 dumpall
 dumpfile
+dumpi
 dumplimit
 dumpoff
 dumpon
@@ -537,6 +549,7 @@ filesystem
 filt
 flto
 flushCall
+fno
 fopen
 forceable
 foreach
@@ -600,6 +613,7 @@ inouts
 inserted
 instantiation
 instantiations
+intra
 iostream
 ish
 isunbounded
@@ -725,6 +739,7 @@ profiler
 prototyptes
 ps
 pthread
+ptr
 pulldown
 pulldowns
 pullup
@@ -750,6 +765,7 @@ reloop
 resetall
 respecified
 rodata
+rolloverSize
 rr
 rst
 runtime
@@ -764,6 +780,7 @@ seg
 setuphold
 sformat
 sformatf
+shareefj
 shortint
 shortreal
 signame
diff --git a/examples/cmake_hello_sc/Makefile b/examples/cmake_hello_sc/Makefile
index 540a32fec..9ec875aba 100644
--- a/examples/cmake_hello_sc/Makefile
+++ b/examples/cmake_hello_sc/Makefile
@@ -39,30 +39,12 @@ ifneq ($(CMAKE_GT_3_8),true)
 TARGET := oldcmake
 else
 
-# Test existence of SYSTEMC_INCLUDE and SYSTEMC_LIBDIR environment variabless
-ifneq (,$(SYSTEMC_INCLUDE))
-ifneq (,${SYSTEMC_LIBDIR})
-SYSTEMC_SET := true
-endif
-endif
-
-# Test existence of SYSTEMC_ROOT environment variable
-ifneq (SYSTEMC_SET, true)
-ifneq (,${SYSTEMC_ROOT})
-SYSTEMC_SET := true
-endif
-endif
-
-# Test existence of SYSTEMC environment variable
-ifneq (SYSTEMC_SET, true)
-ifneq (,${SYSTEMC})
-SYSTEMC_SET := true
-endif
-endif
+# Check if SC exists via a verilator call (empty if not)
+SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC)
 
 # Test whether SystemC is installed with CMake support
 # This will print a CMake error about processing arguments that can (currently) be ignored.
-ifneq (SYSTEMC_SET, true)
+ifneq (,$(SYSTEMC_EXISTS))
 FINDSC := $(shell mkdir -p build && cd build && cmake --find-package -DNAME=SystemCLanguage -DCMAKE_USE_PTHREADS_INIT=ON -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST -DThreads_FOUND=ON)
 ifneq (,$(findstring SystemCLanguage found,$(FINDSC)))
 SYSTEMC_SET := true
diff --git a/examples/cmake_tracing_sc/Makefile b/examples/cmake_tracing_sc/Makefile
index 65868d5bf..fb10802cc 100644
--- a/examples/cmake_tracing_sc/Makefile
+++ b/examples/cmake_tracing_sc/Makefile
@@ -39,30 +39,12 @@ ifneq ($(CMAKE_GT_3_8),true)
 TARGET := oldcmake
 else
 
-# Test existence of SYSTEMC_INCLUDE and SYSTEMC_LIBDIR environment variabless
-ifneq (,$(SYSTEMC_INCLUDE))
-ifneq (,${SYSTEMC_LIBDIR})
-SYSTEMC_SET := true
-endif
-endif
-
-# Test existence of SYSTEMC_ROOT environment variable
-ifneq (SYSTEMC_SET, true)
-ifneq (,${SYSTEMC_ROOT})
-SYSTEMC_SET := true
-endif
-endif
-
-# Test existence of SYSTEMC environment variable
-ifneq (SYSTEMC_SET, true)
-ifneq (,${SYSTEMC})
-SYSTEMC_SET := true
-endif
-endif
+# Check if SC exists via a verilator call (empty if not)
+SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC)
 
 # Test whether SystemC is installed with CMake support
 # This will print a CMake error about processing arguments that can (currently) be ignored.
-ifneq (SYSTEMC_SET, true)
+ifneq (,$(SYSTEMC_EXISTS))
 FINDSC := $(shell mkdir -p build && cd build && cmake --find-package -DNAME=SystemCLanguage -DCMAKE_USE_PTHREADS_INIT=ON -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST -DThreads_FOUND=ON)
 ifneq (,$(findstring SystemCLanguage found,$(FINDSC)))
 SYSTEMC_SET := true
diff --git a/examples/make_hello_binary/.gitignore b/examples/make_hello_binary/.gitignore
new file mode 100644
index 000000000..f68b202e1
--- /dev/null
+++ b/examples/make_hello_binary/.gitignore
@@ -0,0 +1,6 @@
+*.dmp
+*.log
+*.csrc
+*.vcd
+obj_*
+logs
diff --git a/examples/make_hello_binary/Makefile b/examples/make_hello_binary/Makefile
new file mode 100644
index 000000000..19bec2dd0
--- /dev/null
+++ b/examples/make_hello_binary/Makefile
@@ -0,0 +1,49 @@
+######################################################################
+#
+# DESCRIPTION: Verilator Example: Small Makefile
+#
+# This calls the object directory makefile.  That allows the objects to
+# be placed in the "current directory" which simplifies the Makefile.
+#
+# This file ONLY is placed under the Creative Commons Public Domain, for
+# any use, without warranty, 2020 by Wilson Snyder.
+# SPDX-License-Identifier: CC0-1.0
+#
+######################################################################
+# Check for sanity to avoid later confusion
+
+ifneq ($(words $(CURDIR)),1)
+ $(error Unsupported: GNU Make cannot build in directories containing spaces, build elsewhere: '$(CURDIR)')
+endif
+
+######################################################################
+
+# This is intended to be a minimal example.  Before copying this to start a
+# real project, it is better to start with a more complete example,
+# e.g. examples/make_tracing_c.
+
+# If $VERILATOR_ROOT isn't in the environment, we assume it is part of a
+# package install, and verilator is in your path. Otherwise find the
+# binary relative to $VERILATOR_ROOT (such as when inside the git sources).
+ifeq ($(VERILATOR_ROOT),)
+VERILATOR = verilator
+else
+export VERILATOR_ROOT
+VERILATOR = $(VERILATOR_ROOT)/bin/verilator
+endif
+
+default:
+	@echo "-- Verilator hello-world simple binary example"
+	@echo "-- VERILATE & BUILD --------"
+	$(VERILATOR) --binary -j 0 top.v
+	@echo "-- RUN ---------------------"
+	obj_dir/Vtop
+	@echo "-- DONE --------------------"
+	@echo "Note: Once this example is understood, see examples/make_hello_c."
+	@echo "Note: See also https://verilator.org/guide/latest/examples.html"
+
+######################################################################
+
+maintainer-copy::
+clean mostlyclean distclean maintainer-clean::
+	-rm -rf obj_dir *.log *.dmp *.vpd core
diff --git a/examples/make_hello_binary/top.v b/examples/make_hello_binary/top.v
new file mode 100644
index 000000000..3deb48f2d
--- /dev/null
+++ b/examples/make_hello_binary/top.v
@@ -0,0 +1,14 @@
+// DESCRIPTION: Verilator: Verilog example module
+//
+// This file ONLY is placed under the Creative Commons Public Domain, for
+// any use, without warranty, 2017 by Wilson Snyder.
+// SPDX-License-Identifier: CC0-1.0
+
+// See also https://verilator.org/guide/latest/examples.html"
+
+module top;
+   initial begin
+      $display("Hello World!");
+      $finish;
+   end
+endmodule
diff --git a/examples/make_hello_sc/Makefile b/examples/make_hello_sc/Makefile
index 9548d716c..20a26d034 100644
--- a/examples/make_hello_sc/Makefile
+++ b/examples/make_hello_sc/Makefile
@@ -33,7 +33,7 @@ VERILATOR = $(VERILATOR_ROOT)/bin/verilator
 endif
 
 # Check if SC exists via a verilator call (empty if not)
-SYSTEMC_EXISTS := $(shell $(VERILATOR) --getenv SYSTEMC_INCLUDE)
+SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC)
 
 ifneq ($(SYSTEMC_EXISTS),)
 default: run
diff --git a/examples/make_tracing_sc/Makefile b/examples/make_tracing_sc/Makefile
index 5f90a5ebf..cf61c8b7a 100644
--- a/examples/make_tracing_sc/Makefile
+++ b/examples/make_tracing_sc/Makefile
@@ -55,7 +55,7 @@ VERILATOR_FLAGS += --coverage
 VERILATOR_INPUT = -f input.vc top.v sc_main.cpp
 
 # Check if SC exists via a verilator call (empty if not)
-SYSTEMC_EXISTS := $(shell $(VERILATOR) --getenv SYSTEMC_INCLUDE)
+SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC)
 
 ######################################################################
 
diff --git a/include/gtkwave/fstapi.c b/include/gtkwave/fstapi.c
index b19e0bcaa..5e9529d44 100644
--- a/include/gtkwave/fstapi.c
+++ b/include/gtkwave/fstapi.c
@@ -140,7 +140,7 @@ void **JenkinsIns(void *base_i, const unsigned char *mem, uint32_t length, uint3
 #include 
 #endif
 
-#if defined(FST_MACOSX) || defined(__MINGW32__) || defined(__OpenBSD__) || defined(__FreeBSD__)
+#if defined(FST_MACOSX) || defined(__MINGW32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
 #define FST_UNBUFFERED_IO
 #endif
 
diff --git a/include/verilated.cpp b/include/verilated.cpp
index 1b5dab84b..6428c4ee5 100644
--- a/include/verilated.cpp
+++ b/include/verilated.cpp
@@ -905,15 +905,22 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA
                         digits = append.length();
                     }
                     const int needmore = width - digits;
-                    std::string padding;
                     if (needmore > 0) {
-                        if (pctp && pctp[0] && pctp[1] == '0') {  // %0
-                            padding.append(needmore, '0');  // Pre-pad zero
-                        } else {
+                        std::string padding;
+                        if (left) {
                             padding.append(needmore, ' ');  // Pre-pad spaces
+                            output += append + padding;
+                        } else {
+                            if (pctp && pctp[0] && pctp[1] == '0') {  // %0
+                                padding.append(needmore, '0');  // Pre-pad zero
+                            } else {
+                                padding.append(needmore, ' ');  // Pre-pad spaces
+                            }
+                            output += padding + append;
                         }
+                    } else {
+                        output += append;
                     }
-                    output += left ? (append + padding) : (padding + append);
                     break;
                 }
                 case '#': {  // Unsigned decimal
@@ -927,15 +934,22 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA
                         digits = append.length();
                     }
                     const int needmore = width - digits;
-                    std::string padding;
                     if (needmore > 0) {
-                        if (pctp && pctp[0] && pctp[1] == '0') {  // %0
-                            padding.append(needmore, '0');  // Pre-pad zero
-                        } else {
+                        std::string padding;
+                        if (left) {
                             padding.append(needmore, ' ');  // Pre-pad spaces
+                            output += append + padding;
+                        } else {
+                            if (pctp && pctp[0] && pctp[1] == '0') {  // %0
+                                padding.append(needmore, '0');  // Pre-pad zero
+                            } else {
+                                padding.append(needmore, ' ');  // Pre-pad spaces
+                            }
+                            output += padding + append;
                         }
+                    } else {
+                        output += append;
                     }
-                    output += left ? (append + padding) : (padding + append);
                     break;
                 }
                 case 't': {  // Time
@@ -944,21 +958,62 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA
                     output += _vl_vsformat_time(t_tmp, ld, timeunit, left, width);
                     break;
                 }
-                case 'b':
-                    for (; lsb >= 0; --lsb) output += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0';
-                    break;
-                case 'o':
-                    for (; lsb >= 0; --lsb) {
-                        lsb = (lsb / 3) * 3;  // Next digit
-                        // Octal numbers may span more than one wide word,
-                        // so we need to grab each bit separately and check for overrun
-                        // Octal is rare, so we'll do it a slow simple way
-                        output += static_cast(
-                            '0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0)
-                            + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0)
-                            + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0));
+                case 'b':  // FALLTHRU
+                case 'o':  // FALLTHRU
+                case 'x': {
+                    if (widthSet || left) {
+                        lsb = VL_MOSTSETBITP1_W(VL_WORDS_I(lbits), lwp);
+                        lsb = (lsb < 1) ? 0 : (lsb - 1);
+                    }
+
+                    std::string append;
+                    int digits;
+                    switch (fmt) {
+                    case 'b': {
+                        digits = lsb + 1;
+                        for (; lsb >= 0; --lsb) append += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0';
+                        break;
+                    }
+                    case 'o': {
+                        digits = (lsb + 1 + 2) / 3;
+                        for (; lsb >= 0; --lsb) {
+                            lsb = (lsb / 3) * 3;  // Next digit
+                            // Octal numbers may span more than one wide word,
+                            // so we need to grab each bit separately and check for overrun
+                            // Octal is rare, so we'll do it a slow simple way
+                            append += static_cast(
+                                '0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0)
+                                + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0)
+                                + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0));
+                        }
+                        break;
+                    }
+                    default: {  // 'x'
+                        digits = (lsb + 1 + 3) / 4;
+                        for (; lsb >= 0; --lsb) {
+                            lsb = (lsb / 4) * 4;  // Next digit
+                            const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf;
+                            append += "0123456789abcdef"[charval];
+                        }
+                        break;
+                    }
+                    }  // switch
+
+                    const int needmore = width - digits;
+                    if (needmore > 0) {
+                        std::string padding;
+                        if (left) {
+                            padding.append(needmore, ' ');  // Pre-pad spaces
+                            output += append + padding;
+                        } else {
+                            padding.append(needmore, '0');  // Pre-pad zero
+                            output += padding + append;
+                        }
+                    } else {
+                        output += append;
                     }
                     break;
+                }  // b / o / x
                 case 'u':
                 case 'z': {  // Packed 4-state
                     const bool is_4_state = (fmt == 'z');
@@ -984,13 +1039,6 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA
                         }
                     }
                     break;
-                case 'x':
-                    for (; lsb >= 0; --lsb) {
-                        lsb = (lsb / 4) * 4;  // Next digit
-                        const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf;
-                        output += "0123456789abcdef"[charval];
-                    }
-                    break;
                 default: {  // LCOV_EXCL_START
                     const std::string msg = std::string{"Unknown _vl_vsformat code: "} + pos[0];
                     VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str());
@@ -2310,7 +2358,7 @@ std::string VerilatedContext::dumpfile() const VL_MT_SAFE_EXCLUDES(m_timeDumpMut
 std::string VerilatedContext::dumpfileCheck() const VL_MT_SAFE_EXCLUDES(m_timeDumpMutex) {
     std::string out = dumpfile();
     if (VL_UNLIKELY(out.empty())) {
-        VL_PRINTF_MT("%%Warning: $dumpvar ignored as not proceeded by $dumpfile\n");
+        VL_PRINTF_MT("%%Warning: $dumpvar ignored as not preceded by $dumpfile\n");
         return "";
     }
     return out;
@@ -2378,23 +2426,25 @@ void VerilatedContext::timeunit(int value) VL_MT_SAFE {
 }
 void VerilatedContext::timeprecision(int value) VL_MT_SAFE {
     if (value < 0) value = -value;  // Stored as 0..15
-    const VerilatedLockGuard lock{m_mutex};
-    m_s.m_timeprecision = value;
-#ifdef SYSTEMC_VERSION
-    const sc_time sc_res = sc_get_time_resolution();
     int sc_prec = 99;
-    if (sc_res == sc_time(1, SC_SEC)) {
-        sc_prec = 0;
-    } else if (sc_res == sc_time(1, SC_MS)) {
-        sc_prec = 3;
-    } else if (sc_res == sc_time(1, SC_US)) {
-        sc_prec = 6;
-    } else if (sc_res == sc_time(1, SC_NS)) {
-        sc_prec = 9;
-    } else if (sc_res == sc_time(1, SC_PS)) {
-        sc_prec = 12;
-    } else if (sc_res == sc_time(1, SC_FS)) {
-        sc_prec = 15;
+    {
+        const VerilatedLockGuard lock{m_mutex};
+        m_s.m_timeprecision = value;
+#ifdef SYSTEMC_VERSION
+        const sc_time sc_res = sc_get_time_resolution();
+        if (sc_res == sc_time(1, SC_SEC)) {
+            sc_prec = 0;
+        } else if (sc_res == sc_time(1, SC_MS)) {
+            sc_prec = 3;
+        } else if (sc_res == sc_time(1, SC_US)) {
+            sc_prec = 6;
+        } else if (sc_res == sc_time(1, SC_NS)) {
+            sc_prec = 9;
+        } else if (sc_res == sc_time(1, SC_PS)) {
+            sc_prec = 12;
+        } else if (sc_res == sc_time(1, SC_FS)) {
+            sc_prec = 15;
+        }
     }
     if (value != sc_prec) {
         std::ostringstream msg;
@@ -2406,6 +2456,8 @@ void VerilatedContext::timeprecision(int value) VL_MT_SAFE {
         const std::string msgs = msg.str();
         VL_FATAL_MT("", 0, "", msgs.c_str());
     }
+#else
+    }
 #endif
 }
 const char* VerilatedContext::timeunitString() const VL_MT_SAFE { return vl_time_str(timeunit()); }
@@ -2836,7 +2888,7 @@ const char* Verilated::productVersion() VL_PURE { return VERILATOR_VERSION; }
 void Verilated::nullPointerError(const char* filename, int linenum) VL_MT_SAFE {
     // Slowpath - Called only on error
     VL_FATAL_MT(filename, linenum, "", "Null pointer dereferenced");
-    VL_UNREACHABLE
+    VL_UNREACHABLE;
 }
 
 void Verilated::overWidthError(const char* signame) VL_MT_SAFE {
@@ -2844,7 +2896,7 @@ void Verilated::overWidthError(const char* signame) VL_MT_SAFE {
     const std::string msg = (std::string{"Testbench C set input '"} + signame
                              + "' to value that overflows what the signal's width can fit");
     VL_FATAL_MT("unknown", 0, "", msg.c_str());
-    VL_UNREACHABLE
+    VL_UNREACHABLE;
 }
 
 void Verilated::mkdir(const char* dirname) VL_MT_UNSAFE {
@@ -3095,3 +3147,18 @@ void VerilatedAssertOneThread::fatal_different() VL_MT_SAFE {
 #endif
 
 //===========================================================================
+// VlDeleter:: Methods
+
+void VlDeleter::deleteAll() {
+    while (true) {
+        VerilatedLockGuard lock{m_mutex};
+        if (m_newGarbage.empty()) break;
+        VerilatedLockGuard deleteLock{m_deleteMutex};
+        std::swap(m_newGarbage, m_toDelete);
+        lock.unlock();  // So destuctors can enqueue new objects
+        for (VlClass* const objp : m_toDelete) delete objp;
+        m_toDelete.clear();
+    }
+}
+
+//===========================================================================
diff --git a/include/verilated.h b/include/verilated.h
index ed7f7955a..66ae6017b 100644
--- a/include/verilated.h
+++ b/include/verilated.h
@@ -164,7 +164,7 @@ public:
     ~VerilatedMutex() = default;
     const VerilatedMutex& operator!() const { return *this; }  // For -fthread_safety
     /// Acquire/lock mutex
-    void lock() VL_ACQUIRE() {
+    void lock() VL_ACQUIRE() VL_MT_SAFE {
         // Try to acquire the lock by spinning.  If the wait is short,
         // avoids a trap to the OS plus OS scheduler overhead.
         if (VL_LIKELY(try_lock())) return;  // Short circuit loop
@@ -176,9 +176,9 @@ public:
         m_mutex.lock();
     }
     /// Release/unlock mutex
-    void unlock() VL_RELEASE() { m_mutex.unlock(); }
+    void unlock() VL_RELEASE() VL_MT_SAFE { m_mutex.unlock(); }
     /// Try to acquire mutex.  Returns true on success, and false on failure.
-    bool try_lock() VL_TRY_ACQUIRE(true) { return m_mutex.try_lock(); }
+    bool try_lock() VL_TRY_ACQUIRE(true) VL_MT_SAFE { return m_mutex.try_lock(); }
 };
 
 /// Lock guard for mutex (ala std::unique_lock), wrapped to allow -fthread_safety checks
@@ -190,16 +190,16 @@ private:
 
 public:
     /// Construct and hold given mutex lock until destruction or unlock()
-    explicit VerilatedLockGuard(VerilatedMutex& mutexr) VL_ACQUIRE(mutexr)
+    explicit VerilatedLockGuard(VerilatedMutex& mutexr) VL_ACQUIRE(mutexr) VL_MT_SAFE
         : m_mutexr(mutexr) {  // Need () or GCC 4.8 false warning
         m_mutexr.lock();
     }
     /// Destruct and unlock the mutex
     ~VerilatedLockGuard() VL_RELEASE() { m_mutexr.unlock(); }
     /// Unlock the mutex
-    void lock() VL_ACQUIRE() { m_mutexr.lock(); }
+    void lock() VL_ACQUIRE() VL_MT_SAFE { m_mutexr.lock(); }
     /// Lock the mutex
-    void unlock() VL_RELEASE() { m_mutexr.unlock(); }
+    void unlock() VL_RELEASE() VL_MT_SAFE { m_mutexr.unlock(); }
 };
 
 #else  // !VL_THREADED
diff --git a/include/verilated.mk.in b/include/verilated.mk.in
index c86e93676..b01799dfb 100644
--- a/include/verilated.mk.in
+++ b/include/verilated.mk.in
@@ -27,6 +27,8 @@ CFG_CXXFLAGS_STD_NEWEST = @CFG_CXXFLAGS_STD_NEWEST@
 CFG_CXXFLAGS_NO_UNUSED = @CFG_CXXFLAGS_NO_UNUSED@
 # Compiler flags that turn on extra warnings
 CFG_CXXFLAGS_WEXTRA = @CFG_CXXFLAGS_WEXTRA@
+# Compiler flags that enable coroutine support
+CFG_CXXFLAGS_COROUTINES = @CFG_CXXFLAGS_COROUTINES@
 # Linker libraries for multithreading
 CFG_LDLIBS_THREADS = @CFG_LDLIBS_THREADS@
 
@@ -142,6 +144,12 @@ ifneq ($(VM_THREADS),0)
  endif
 endif
 
+ifneq ($(VM_TIMING),0)
+ ifneq ($(VM_TIMING),)
+  CPPFLAGS += $(CFG_CXXFLAGS_COROUTINES)
+ endif
+endif
+
 ifneq ($(VK_C11),0)
  ifneq ($(VK_C11),)
   # Need C++11 at least, so always default to newest
diff --git a/include/verilated_cov.cpp b/include/verilated_cov.cpp
index 3932afef6..c442bbae7 100644
--- a/include/verilated_cov.cpp
+++ b/include/verilated_cov.cpp
@@ -357,9 +357,6 @@ public:
     void write(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) {
         Verilated::quiesce();
         const VerilatedLockGuard lock{m_mutex};
-#ifndef VM_COVERAGE
-        VL_FATAL_MT("", 0, "", "%Error: Called VerilatedCov::write when VM_COVERAGE disabled");
-#endif
         selftest();
 
         std::ofstream os{filename};
@@ -518,7 +515,7 @@ VerilatedCovContext* VerilatedContext::coveragep() VL_MT_SAFE {
     if (VL_UNLIKELY(!m_coveragep)) {
         const VerilatedLockGuard lock{s_mutex};
         // cppcheck-suppress identicalInnerCondition
-        if (VL_LIKELY(!m_coveragep)) {  // Not redundant, prevents race
+        if (VL_LIKELY(!m_coveragep)) {  // LCOV_EXCL_LINE // Not redundant, prevents race
             m_coveragep.reset(new VerilatedCovImp);
         }
     }
diff --git a/include/verilated_cov.h b/include/verilated_cov.h
index 4c3b1550d..4697c4f88 100644
--- a/include/verilated_cov.h
+++ b/include/verilated_cov.h
@@ -35,22 +35,6 @@
 
 class VerilatedCovImp;
 
-//=============================================================================
-/// Conditionally compile statements only when doing coverage (when
-/// VM_COVERAGE is defined)
-
-// clang-format off
-#ifdef VM_COVERAGE
-# define VL_IF_COVER(stmts) \
-    do { stmts; } while (false)
-#else
-# define VL_IF_COVER(stmts) \
-    do { \
-        if (false) { stmts; } \
-    } while (false)
-#endif
-// clang-format on
-
 //=============================================================================
 /// Insert a item for coverage analysis.
 /// The first argument is a pointer to the count to be dumped.
@@ -83,8 +67,11 @@ class VerilatedCovImp;
 ///     }
 
 #define VL_COVER_INSERT(covcontextp, countp, ...) \
-    VL_IF_COVER(covcontextp->_inserti(countp); covcontextp->_insertf(__FILE__, __LINE__); \
-                covcontextp->_insertp("hier", name(), __VA_ARGS__))
+    do { \
+        covcontextp->_inserti(countp); \
+        covcontextp->_insertf(__FILE__, __LINE__); \
+        covcontextp->_insertp("hier", name(), __VA_ARGS__); \
+    } while (false)
 
 //=============================================================================
 // Convert VL_COVER_INSERT value arguments to strings, is \internal
diff --git a/include/verilated_fst_sc.h b/include/verilated_fst_sc.h
index f5aaa305c..a34d72d0b 100644
--- a/include/verilated_fst_sc.h
+++ b/include/verilated_fst_sc.h
@@ -84,6 +84,7 @@ private:
 
     // clang-format off
     // Formatting matches that of sc_trace.h
+    // LCOV_EXCL_START
 #if (SYSTEMC_VERSION >= 20171012)
     DECL_TRACE_METHOD_A( sc_event )
     DECL_TRACE_METHOD_A( sc_time )
@@ -118,6 +119,7 @@ private:
 
     DECL_TRACE_METHOD_A( sc_dt::sc_bv_base )
     DECL_TRACE_METHOD_A( sc_dt::sc_lv_base )
+    // LCOV_EXCL_STOP
     // clang-format on
 
 #undef DECL_TRACE_METHOD_A
diff --git a/include/verilated_threads.cpp b/include/verilated_threads.cpp
index 9d6bdedc9..40adba4bc 100644
--- a/include/verilated_threads.cpp
+++ b/include/verilated_threads.cpp
@@ -58,7 +58,7 @@ VlWorkerThread::~VlWorkerThread() {
     m_cthread.join();
 }
 
-static void shutdownTask(void*, bool) {
+static void shutdownTask(void*, bool) {  // LCOV_EXCL_LINE
     // Deliberately empty, we use the address of this function as a magic number
 }
 
diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp
new file mode 100644
index 000000000..f644ed64f
--- /dev/null
+++ b/include/verilated_timing.cpp
@@ -0,0 +1,223 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+//
+// Code available from: https://verilator.org
+//
+// Copyright 2022 by Wilson Snyder. This program is free software; you can
+// redistribute it and/or modify it under the terms of either the GNU Lesser
+// General Public License Version 3 or the Perl Artistic License Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//=========================================================================
+///
+/// \file
+/// \brief Verilated timing implementation code
+///
+/// This file must be compiled and linked against all Verilated objects
+/// that use timing features.
+///
+/// See the internals documentation docs/internals.rst for details.
+///
+//=========================================================================
+
+#include "verilated_timing.h"
+
+//======================================================================
+// VlCoroutineHandle:: Methods
+
+void VlCoroutineHandle::resume() {
+    // Only null if we have a fork..join_any and one of the other child processes resumed the
+    // main process
+    if (VL_LIKELY(m_coro)) {
+        VL_DEBUG_IF(VL_DBG_MSGF("             Resuming: "); dump(););
+        m_coro();
+        m_coro = nullptr;
+    }
+}
+
+#ifdef VL_DEBUG
+void VlCoroutineHandle::dump() const {
+    VL_PRINTF("Process waiting at %s:%d\n", m_fileline.filename(), m_fileline.lineno());
+}
+#endif
+
+//======================================================================
+// VlDelayScheduler:: Methods
+
+#ifdef VL_DEBUG
+void VlDelayScheduler::VlDelayedCoroutine::dump() const {
+    VL_DBG_MSGF("             Awaiting time %" PRIu64 ": ", m_timestep);
+    m_handle.dump();
+}
+#endif
+
+void VlDelayScheduler::resume() {
+#ifdef VL_DEBUG
+    VL_DEBUG_IF(dump(); VL_DBG_MSGF("         Resuming delayed processes\n"););
+#endif
+    while (awaitingCurrentTime()) {
+        if (m_queue.front().m_timestep != m_context.time()) {
+            VL_FATAL_MT(__FILE__, __LINE__, "",
+                        "%Error: Encountered process that should've been resumed at an "
+                        "earlier simulation time. Missed a time slot?");
+        }
+        // Move max element in the heap to the end
+        std::pop_heap(m_queue.begin(), m_queue.end());
+        VlCoroutineHandle handle = std::move(m_queue.back().m_handle);
+        m_queue.pop_back();
+        handle.resume();
+    }
+}
+
+uint64_t VlDelayScheduler::nextTimeSlot() const {
+    if (empty()) {
+        VL_FATAL_MT(__FILE__, __LINE__, "", "%Error: There is no next time slot scheduled");
+    }
+    return m_queue.front().m_timestep;
+}
+
+#ifdef VL_DEBUG
+void VlDelayScheduler::dump() const {
+    if (m_queue.empty()) {
+        VL_DBG_MSGF("         No delayed processes:\n");
+    } else {
+        VL_DBG_MSGF("         Delayed processes:\n");
+        for (const auto& susp : m_queue) susp.dump();
+    }
+}
+#endif
+
+//======================================================================
+// VlTriggerScheduler:: Methods
+
+void VlTriggerScheduler::resume(const char* eventDescription) {
+#ifdef VL_DEBUG
+    VL_DEBUG_IF(dump(eventDescription);
+                VL_DBG_MSGF("         Resuming processes waiting for %s\n", eventDescription););
+#endif
+    for (auto& susp : m_ready) susp.resume();
+    m_ready.clear();
+    commit(eventDescription);
+}
+
+void VlTriggerScheduler::commit(const char* eventDescription) {
+#ifdef VL_DEBUG
+    if (!m_uncommitted.empty()) {
+        VL_DEBUG_IF(
+            VL_DBG_MSGF("         Committing processes waiting for %s:\n", eventDescription);
+            for (const auto& susp
+                 : m_uncommitted) {
+                VL_DBG_MSGF("           - ");
+                susp.dump();
+            });
+    }
+#endif
+    m_ready.reserve(m_ready.size() + m_uncommitted.size());
+    m_ready.insert(m_ready.end(), std::make_move_iterator(m_uncommitted.begin()),
+                   std::make_move_iterator(m_uncommitted.end()));
+    m_uncommitted.clear();
+}
+
+#ifdef VL_DEBUG
+void VlTriggerScheduler::dump(const char* eventDescription) const {
+    if (m_ready.empty()) {
+        VL_DBG_MSGF("         No ready processes waiting for %s\n", eventDescription);
+    } else {
+        for (const auto& susp : m_ready) {
+            VL_DBG_MSGF("         Ready processes waiting for %s:\n", eventDescription);
+            VL_DBG_MSGF("           - ");
+            susp.dump();
+        }
+    }
+    if (!m_uncommitted.empty()) {
+        VL_DBG_MSGF("         Uncommitted processes waiting for %s:\n", eventDescription);
+        for (const auto& susp : m_uncommitted) {
+            VL_DBG_MSGF("           - ");
+            susp.dump();
+        }
+    }
+}
+#endif
+
+//======================================================================
+// VlDynamicTriggerScheduler:: Methods
+
+bool VlDynamicTriggerScheduler::evaluate() {
+    VL_DEBUG_IF(dump(););
+    std::swap(m_suspended, m_evaluated);
+    for (auto& coro : m_evaluated) coro.resume();
+    m_evaluated.clear();
+    return !m_triggered.empty();
+}
+
+void VlDynamicTriggerScheduler::doPostUpdates() {
+    VL_DEBUG_IF(if (!m_post.empty())
+                    VL_DBG_MSGF("         Doing post updates for processes:\n");  //
+                for (const auto& susp
+                     : m_post) {
+                    VL_DBG_MSGF("           - ");
+                    susp.dump();
+                });
+    for (auto& coro : m_post) coro.resume();
+    m_post.clear();
+}
+
+void VlDynamicTriggerScheduler::resume() {
+    VL_DEBUG_IF(if (!m_triggered.empty()) VL_DBG_MSGF("         Resuming processes:\n");  //
+                for (const auto& susp
+                     : m_triggered) {
+                    VL_DBG_MSGF("           - ");
+                    susp.dump();
+                });
+    for (auto& coro : m_triggered) coro.resume();
+    m_triggered.clear();
+}
+
+#ifdef VL_DEBUG
+void VlDynamicTriggerScheduler::dump() const {
+    if (m_suspended.empty()) {
+        VL_DBG_MSGF("         No suspended processes waiting for dynamic trigger evaluation\n");
+    } else {
+        for (const auto& susp : m_suspended) {
+            VL_DBG_MSGF("         Suspended processes waiting for dynamic trigger evaluation:\n");
+            VL_DBG_MSGF("           - ");
+            susp.dump();
+        }
+    }
+}
+#endif
+
+//======================================================================
+// VlForkSync:: Methods
+
+void VlForkSync::done(const char* filename, int lineno) {
+    VL_DEBUG_IF(VL_DBG_MSGF("             Process forked at %s:%d finished\n", filename, lineno););
+    if (m_join->m_counter > 0) m_join->m_counter--;
+    if (m_join->m_counter == 0) m_join->m_susp.resume();
+}
+
+//======================================================================
+// VlCoroutine:: Methods
+
+VlCoroutine::VlPromise::~VlPromise() {
+    // Indicate to the return object that the coroutine has finished or been destroyed
+    if (m_corop) m_corop->m_promisep = nullptr;
+    // If there is a continuation, destroy it
+    if (m_continuation) m_continuation.destroy();
+}
+
+std::suspend_never VlCoroutine::VlPromise::final_suspend() noexcept {
+    // Indicate to the return object that the coroutine has finished
+    if (m_corop) {
+        m_corop->m_promisep = nullptr;
+        // Forget the return value, we won't need it and it won't be able to let us know if
+        // it's destroyed
+        m_corop = nullptr;
+    }
+    // If there is a continuation, resume it
+    if (m_continuation) {
+        m_continuation();
+        m_continuation = nullptr;
+    }
+    return {};
+}
diff --git a/include/verilated_timing.h b/include/verilated_timing.h
new file mode 100644
index 000000000..f39d88ed6
--- /dev/null
+++ b/include/verilated_timing.h
@@ -0,0 +1,429 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+//
+// Code available from: https://verilator.org
+//
+// Copyright 2022 by Wilson Snyder. This program is free software; you can
+// redistribute it and/or modify it under the terms of either the GNU Lesser
+// General Public License Version 3 or the Perl Artistic License Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+///
+/// \file
+/// \brief Verilated timing header
+///
+/// This file is included automatically by Verilator in some of the C++ files
+/// it generates if timing features are used.
+///
+/// This file is not part of the Verilated public-facing API.
+/// It is only for internal use.
+///
+/// See the internals documentation docs/internals.rst for details.
+///
+//*************************************************************************
+#ifndef VERILATOR_VERILATED_TIMING_H_
+#define VERILATOR_VERILATED_TIMING_H_
+
+#include "verilated.h"
+
+// clang-format off
+// Some preprocessor magic to support both Clang and GCC coroutines with both libc++ and libstdc++
+#if defined _LIBCPP_VERSION  // libc++
+# if __clang_major__ > 13  // Clang > 13 warns that coroutine types in std::experimental are deprecated
+#  pragma clang diagnostic push
+#  pragma clang diagnostic ignored "-Wdeprecated-experimental-coroutine"
+# endif
+# include 
+  namespace std {
+      using namespace experimental; // Bring std::experimental into the std namespace
+  }
+#else
+# if defined __clang__ && defined __GLIBCXX__
+#  define __cpp_impl_coroutine 1  // Clang doesn't define this, but it's needed for libstdc++
+# endif
+# include 
+# if __clang_major__ < 14
+   namespace std { // Bring coroutine library into std::experimental, as Clang < 14 expects it to be there
+       namespace experimental {
+           using namespace std;
+       }
+   }
+# endif
+#endif
+// clang-format on
+
+// Placeholder for compiling with --protect-ids
+#define VL_UNKNOWN ""
+
+//=============================================================================
+// VlFileLineDebug stores a SystemVerilog source code location. Used in VlCoroutineHandle for
+// debugging purposes.
+
+class VlFileLineDebug final {
+    // MEMBERS
+#ifdef VL_DEBUG
+    const char* m_filename = nullptr;
+    int m_lineno = 0;
+#endif
+
+public:
+    // CONSTRUCTORS
+    // Construct
+    VlFileLineDebug() = default;
+    VlFileLineDebug(const char* filename, int lineno)
+#ifdef VL_DEBUG
+        : m_filename{filename}
+        , m_lineno{lineno}
+#endif
+    {
+    }
+
+    // METHODS
+#ifdef VL_DEBUG
+    const char* filename() const { return m_filename; }
+    int lineno() const { return m_lineno; }
+#endif
+};
+
+//=============================================================================
+// VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is
+// cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got
+// suspended, another VlCoroutineHandle was created to manage it.
+
+class VlCoroutineHandle final {
+    VL_UNCOPYABLE(VlCoroutineHandle);
+
+    // MEMBERS
+    std::coroutine_handle<> m_coro;  // The wrapped coroutine handle
+    VlFileLineDebug m_fileline;
+
+public:
+    // CONSTRUCTORS
+    // Construct
+    VlCoroutineHandle()
+        : m_coro{nullptr} {}
+    VlCoroutineHandle(std::coroutine_handle<> coro, VlFileLineDebug fileline)
+        : m_coro{coro}
+        , m_fileline{fileline} {}
+    // Move the handle, leaving a nullptr
+    VlCoroutineHandle(VlCoroutineHandle&& moved)
+        : m_coro{std::exchange(moved.m_coro, nullptr)}
+        , m_fileline{moved.m_fileline} {}
+    // Destroy if the handle isn't null
+    ~VlCoroutineHandle() {
+        // Usually these coroutines should get resumed; we only need to clean up if we destroy a
+        // model with some coroutines suspended
+        if (VL_UNLIKELY(m_coro)) m_coro.destroy();
+    }
+    // METHODS
+    // Move the handle, leaving a null handle
+    auto& operator=(VlCoroutineHandle&& moved) {
+        m_coro = std::exchange(moved.m_coro, nullptr);
+        return *this;
+    }
+    // Resume the coroutine if the handle isn't null
+    void resume();
+#ifdef VL_DEBUG
+    void dump() const;
+#endif
+};
+
+//=============================================================================
+// VlDelayScheduler stores coroutines to be resumed at a certain simulation time. If the current
+// time is equal to a coroutine's resume time, the coroutine gets resumed.
+
+class VlDelayScheduler final {
+    // TYPES
+    struct VlDelayedCoroutine {
+        uint64_t m_timestep;  // Simulation time when the coroutine should be resumed
+        VlCoroutineHandle m_handle;  // The suspended coroutine to be resumed
+
+        // Comparison operator for std::push_heap(), std::pop_heap()
+        bool operator<(const VlDelayedCoroutine& other) const {
+            return m_timestep > other.m_timestep;
+        }
+#ifdef VL_DEBUG
+        void dump() const;
+#endif
+    };
+    using VlDelayedCoroutineQueue = std::vector;
+
+    // MEMBERS
+    VerilatedContext& m_context;
+    VlDelayedCoroutineQueue m_queue;  // Coroutines to be restored at a certain simulation time
+
+public:
+    // CONSTRUCTORS
+    explicit VlDelayScheduler(VerilatedContext& context)
+        : m_context{context} {}
+    // METHODS
+    // Resume coroutines waiting for the current simulation time
+    void resume();
+    // Returns the simulation time of the next time slot (aborts if there are no delayed
+    // coroutines)
+    uint64_t nextTimeSlot() const;
+    // Are there no delayed coroutines awaiting?
+    bool empty() const { return m_queue.empty(); }
+    // Are there coroutines to resume at the current simulation time?
+    bool awaitingCurrentTime() const {
+        return !empty() && m_queue.front().m_timestep <= m_context.time();
+    }
+#ifdef VL_DEBUG
+    void dump() const;
+#endif
+    // Used by coroutines for co_awaiting a certain simulation time
+    auto delay(uint64_t delay, const char* filename = VL_UNKNOWN, int lineno = 0) {
+        struct Awaitable {
+            VlDelayedCoroutineQueue& queue;
+            uint64_t delay;
+            VlFileLineDebug fileline;
+
+            bool await_ready() const { return false; }  // Always suspend
+            void await_suspend(std::coroutine_handle<> coro) {
+                queue.push_back({delay, VlCoroutineHandle{coro, fileline}});
+                // Move last element to the proper place in the max-heap
+                std::push_heap(queue.begin(), queue.end());
+            }
+            void await_resume() const {}
+        };
+        return Awaitable{m_queue, m_context.time() + delay, VlFileLineDebug{filename, lineno}};
+    }
+};
+
+//=============================================================================
+// VlTriggerScheduler stores coroutines to be resumed by a trigger. It does not keep track of its
+// trigger, relying on calling code to resume when appropriate. Coroutines are kept in two stages
+// - 'uncommitted' and 'ready'. Whenever a coroutine is suspended, it lands in the 'uncommited'
+// stage. Only when commit() is called, these coroutines get moved to the 'ready' stage. That's
+// when they can be resumed. This is done to avoid resuming processes before they start waiting.
+
+class VlTriggerScheduler final {
+    // TYPES
+    using VlCoroutineVec = std::vector;
+
+    // MEMBERS
+    VlCoroutineVec m_uncommitted;  // Coroutines suspended before commit() was called
+                                   // (not resumable)
+    VlCoroutineVec m_ready;  // Coroutines that can be resumed (all coros from m_uncommitted are
+                             // moved here in commit())
+
+public:
+    // METHODS
+    // Resumes all coroutines from the 'ready' stage
+    void resume(const char* eventDescription = VL_UNKNOWN);
+    // Moves all coroutines from m_uncommitted to m_ready
+    void commit(const char* eventDescription = VL_UNKNOWN);
+    // Are there no coroutines awaiting?
+    bool empty() const { return m_ready.empty() && m_uncommitted.empty(); }
+#ifdef VL_DEBUG
+    void dump(const char* eventDescription) const;
+#endif
+    // Used by coroutines for co_awaiting a certain trigger
+    auto trigger(const char* eventDescription = VL_UNKNOWN, const char* filename = VL_UNKNOWN,
+                 int lineno = 0) {
+        VL_DEBUG_IF(VL_DBG_MSGF("         Suspending process waiting for %s at %s:%d\n",
+                                eventDescription, filename, lineno););
+        struct Awaitable {
+            VlCoroutineVec& suspended;  // Coros waiting on trigger
+            VlFileLineDebug fileline;
+
+            bool await_ready() const { return false; }  // Always suspend
+            void await_suspend(std::coroutine_handle<> coro) {
+                suspended.emplace_back(coro, fileline);
+            }
+            void await_resume() const {}
+        };
+        return Awaitable{m_uncommitted, VlFileLineDebug{filename, lineno}};
+    }
+};
+
+//=============================================================================
+// VlDynamicTriggerScheduler is used for cases where triggers cannot be statically referenced and
+// evaluated. Coroutines that make use of this scheduler must adhere to a certain procedure:
+//     __Vtrigger = 0;
+//     
+//     while (!__Vtrigger) {
+//         co_await __VdynSched.evaluation();
+//         
;
+//         __Vtrigger = ;
+//         [optionally] co_await __VdynSched.postUpdate();
+//         ;
+//     }
+//    co_await __VdynSched.resumption();
+// The coroutines get resumed at trigger evaluation time, evaluate their local triggers, optionally
+// await the post update step, and if the trigger is set, await proper resumption in the 'act' eval
+// step.
+
+class VlDynamicTriggerScheduler final {
+    // TYPES
+    using VlCoroutineVec = std::vector;
+
+    // MEMBERS
+    VlCoroutineVec m_suspended;  // Suspended coroutines awaiting trigger evaluation
+    VlCoroutineVec m_evaluated;  // Coroutines currently being evaluated (for evaluate())
+    VlCoroutineVec m_triggered;  // Coroutines whose triggers were set, and are awaiting resumption
+    VlCoroutineVec m_post;  // Coroutines awaiting the post update step (only relevant for triggers
+                            // with destructive post updates, e.g. named events)
+
+    // METHODS
+    auto awaitable(VlCoroutineVec& queue, const char* filename, int lineno) {
+        struct Awaitable {
+            VlCoroutineVec& suspended;  // Coros waiting on trigger
+            VlFileLineDebug fileline;
+
+            bool await_ready() const { return false; }  // Always suspend
+            void await_suspend(std::coroutine_handle<> coro) {
+                suspended.emplace_back(coro, fileline);
+            }
+            void await_resume() const {}
+        };
+        return Awaitable{queue, VlFileLineDebug{filename, lineno}};
+    }
+
+public:
+    // Evaluates all dynamic triggers (resumed coroutines that co_await evaluation())
+    bool evaluate();
+    // Runs post updates for all dynamic triggers (resumes coroutines that co_await postUpdate())
+    void doPostUpdates();
+    // Resumes all coroutines whose triggers are set (those that co_await resumption())
+    void resume();
+#ifdef VL_DEBUG
+    void dump() const;
+#endif
+    // Used by coroutines for co_awaiting trigger evaluation
+    auto evaluation(const char* eventDescription, const char* filename, int lineno) {
+        VL_DEBUG_IF(VL_DBG_MSGF("         Suspending process waiting for %s at %s:%d\n",
+                                eventDescription, filename, lineno););
+        return awaitable(m_suspended, filename, lineno);
+    }
+    // Used by coroutines for co_awaiting the trigger post update step
+    auto postUpdate(const char* eventDescription, const char* filename, int lineno) {
+        VL_DEBUG_IF(
+            VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting the post update step\n",
+                        eventDescription, filename, lineno););
+        return awaitable(m_post, filename, lineno);
+    }
+    // Used by coroutines for co_awaiting the resumption step (in 'act' eval)
+    auto resumption(const char* eventDescription, const char* filename, int lineno) {
+        VL_DEBUG_IF(VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting resumption\n",
+                                eventDescription, filename, lineno););
+        return awaitable(m_triggered, filename, lineno);
+    }
+};
+
+//=============================================================================
+// VlNow is a helper awaitable type that always suspends, and then immediately resumes a coroutine.
+// Allows forcing the move of coroutine locals to the heap.
+
+struct VlNow {
+    bool await_ready() const { return false; }  // Always suspend
+    bool await_suspend(std::coroutine_handle<>) const { return false; }  // Resume immediately
+    void await_resume() const {}
+};
+
+//=============================================================================
+// VlForever is a helper awaitable type for suspending coroutines forever. Used for constant
+// wait statements.
+
+struct VlForever {
+    bool await_ready() const { return false; }  // Always suspend
+    void await_suspend(std::coroutine_handle<> coro) const { coro.destroy(); }
+    void await_resume() const {}
+};
+
+//=============================================================================
+// VlForkSync is used to manage fork..join and fork..join_any constructs.
+
+class VlForkSync final {
+    // VlJoin stores the handle of a suspended coroutine that did a fork..join or fork..join_any.
+    // If the counter reaches 0, the suspended coroutine shall be resumed.
+    struct VlJoin {
+        size_t m_counter = 0;  // When reaches 0, resume suspended coroutine
+        VlCoroutineHandle m_susp;  // Coroutine to resume
+    };
+
+    // The join info is shared among all forked processes
+    std::shared_ptr m_join;
+
+public:
+    // Create the join object and set the counter to the specified number
+    void init(size_t count) { m_join.reset(new VlJoin{count, {}}); }
+    // Called whenever any of the forked processes finishes. If the join counter reaches 0, the
+    // main process gets resumed
+    void done(const char* filename = VL_UNKNOWN, int lineno = 0);
+    // Used by coroutines for co_awaiting a join
+    auto join(const char* filename = VL_UNKNOWN, int lineno = 0) {
+        assert(m_join);
+        VL_DEBUG_IF(
+            VL_DBG_MSGF("             Awaiting join of fork at: %s:%d\n", filename, lineno););
+        struct Awaitable {
+            const std::shared_ptr join;  // Join to await on
+            VlFileLineDebug fileline;
+
+            bool await_ready() { return join->m_counter == 0; }  // Suspend if join still exists
+            void await_suspend(std::coroutine_handle<> coro) { join->m_susp = {coro, fileline}; }
+            void await_resume() const {}
+        };
+        return Awaitable{m_join, VlFileLineDebug{filename, lineno}};
+    }
+};
+
+//=============================================================================
+// VlCoroutine
+// Return value of a coroutine. Used for chaining coroutine suspension/resumption.
+
+class VlCoroutine final {
+private:
+    // TYPES
+    struct VlPromise {
+        std::coroutine_handle<> m_continuation;  // Coroutine to resume after this one finishes
+        VlCoroutine* m_corop = nullptr;  // Pointer to the coroutine return object
+
+        ~VlPromise();
+
+        VlCoroutine get_return_object() { return {this}; }
+
+        // Never suspend at the start of the coroutine
+        std::suspend_never initial_suspend() const { return {}; }
+
+        // Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up
+        // after itself)
+        std::suspend_never final_suspend() noexcept;
+
+        void unhandled_exception() const { std::abort(); }
+        void return_void() const {}
+    };
+
+    // MEMBERS
+    VlPromise* m_promisep;  // The promise created for this coroutine
+
+public:
+    // TYPES
+    using promise_type = VlPromise;  // promise_type has to be public
+
+    // CONSTRUCTORS
+    // Construct
+    VlCoroutine(VlPromise* promisep)
+        : m_promisep{promisep} {
+        m_promisep->m_corop = this;
+    }
+    // Move. Update the pointers each time the return object is moved
+    VlCoroutine(VlCoroutine&& other)
+        : m_promisep{std::exchange(other.m_promisep, nullptr)} {
+        if (m_promisep) m_promisep->m_corop = this;
+    }
+    ~VlCoroutine() {
+        // Indicate to the promise that the return object is gone
+        if (m_promisep) m_promisep->m_corop = nullptr;
+    }
+
+    // METHODS
+    // Suspend the awaiter if the coroutine is suspended (the promise exists)
+    bool await_ready() const noexcept { return !m_promisep; }
+    // Set the awaiting coroutine as the continuation of the current coroutine
+    void await_suspend(std::coroutine_handle<> coro) { m_promisep->m_continuation = coro; }
+    void await_resume() const noexcept {}
+};
+
+#endif  // Guard
diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h
index 90dcc2e70..21756dc77 100644
--- a/include/verilated_trace_imp.h
+++ b/include/verilated_trace_imp.h
@@ -644,6 +644,10 @@ void VerilatedTrace::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD
         // Assert no buffer overflow
         assert(m_offloadBufferWritep - bufferp <= m_offloadBufferSize);
 
+        // Reset our pointers as we are giving up the buffer
+        m_offloadBufferWritep = nullptr;
+        m_offloadBufferEndp = nullptr;
+
         // Pass it to the worker thread
         m_offloadBuffersToWorker.put(bufferp);
     }
diff --git a/include/verilated_types.h b/include/verilated_types.h
index d11aee5d7..dba797b74 100644
--- a/include/verilated_types.h
+++ b/include/verilated_types.h
@@ -29,6 +29,8 @@
 #endif
 
 #include 
+#include 
+#include 
 #include 
 #include 
 #include 
@@ -70,6 +72,72 @@ extern std::string VL_TO_STRING_W(int words, const WDataInP obj);
 #define VL_OUT(name, msb, lsb) IData name  ///< Declare output signal, 17-32 bits
 #define VL_OUTW(name, msb, lsb, words) VlWide name  ///< Declare output signal, 65+ bits
 
+//===================================================================
+// Activity trigger vector
+
+template   //
+class VlTriggerVec final {
+    // TODO: static assert T_size > 0, and don't generate when empty
+private:
+    // MEMBERS
+    std::array m_flags;  // State of the assoc array
+
+public:
+    // CONSTRUCTOR
+    VlTriggerVec() { clear(); }
+    ~VlTriggerVec() = default;
+
+    // METHODS
+
+    // Set all elements to false
+    void clear() { m_flags.fill(false); }
+
+    // Reference to element at 'index'
+    bool& at(size_t index) { return m_flags.at(index); }
+
+    // Return true iff at least one element is set
+    bool any() const {
+        for (size_t i = 0; i < m_flags.size(); ++i)
+            if (m_flags[i]) return true;
+        return false;
+    }
+
+    // Set all elements true in 'this' that are set in 'other'
+    void set(const VlTriggerVec& other) {
+        for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] |= other.m_flags[i];
+    }
+
+    // Set elements of 'this' to 'a & !b' element-wise
+    void andNot(const VlTriggerVec& a, const VlTriggerVec& b) {
+        for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] = a.m_flags[i] && !b.m_flags[i];
+    }
+};
+
+//===================================================================
+// SystemVerilog event type
+
+class VlEvent final {
+    // MEMBERS
+    bool m_fired = false;  // Fired on this scheduling iteration
+    bool m_triggered = false;  // Triggered state of event persisting until next time step
+
+public:
+    // CONSTRUCTOR
+    VlEvent() = default;
+    ~VlEvent() = default;
+
+    // METHODS
+    void fire() { m_fired = m_triggered = true; }
+    bool isFired() const { return m_fired; }
+    bool isTriggered() const { return m_triggered; }
+    void clearFired() { m_fired = false; }
+    void clearTriggered() { m_triggered = false; }
+};
+
+inline std::string VL_TO_STRING(const VlEvent& e) {
+    return std::string("triggered=") + (e.isTriggered() ? "true" : "false");
+}
+
 //===================================================================
 // Shuffle RNG
 
@@ -146,6 +214,12 @@ struct VlWide final {
     // Default copy assignment operators are used.
     operator WDataOutP() { return &m_storage[0]; }  // This also allows []
     operator WDataInP() const { return &m_storage[0]; }  // This also allows []
+    bool operator!=(const VlWide& that) const {
+        for (size_t i = 0; i < T_Words; ++i) {
+            if (m_storage[i] != that.m_storage[i]) return true;
+        }
+        return false;
+    }
 
     // METHODS
     const EData& at(size_t index) const { return m_storage[index]; }
@@ -891,6 +965,12 @@ struct VlUnpacked final {
     T_Value& operator[](size_t index) { return m_storage[index]; }
     const T_Value& operator[](size_t index) const { return m_storage[index]; }
 
+    // *this != that, which might be used for change detection/trigger computation, but avoid
+    // operator overloading in VlUnpacked for safety in other contexts.
+    bool neq(const VlUnpacked& that) const { return neq(*this, that); }
+    // Similar to 'neq' above, *this = that used for change detection
+    void assign(const VlUnpacked& that) { *this = that; }
+
     // Dumping. Verilog: str = $sformatf("%p", assoc)
     std::string to_string() const {
         std::string out = "'{";
@@ -901,6 +981,22 @@ struct VlUnpacked final {
         }
         return out + "} ";
     }
+
+private:
+    template 
+    static bool neq(const VlUnpacked& a, const VlUnpacked& b) {
+        for (size_t i = 0; i < T_Dep; ++i) {
+            // Recursive 'neq', in case T_Val is also a VlUnpacked<_, _>
+            if (neq(a.m_storage[i], b.m_storage[i])) return true;
+        }
+        return false;
+    }
+
+    template   //
+    static bool neq(const T_Other& a, const T_Other& b) {
+        // Base case (T_Other is not VlUnpacked<_, _>), fall back on !=
+        return a != b;
+    }
 };
 
 template 
@@ -908,12 +1004,179 @@ std::string VL_TO_STRING(const VlUnpacked& obj) {
     return obj.to_string();
 }
 
+class VlClass;  // See below
+
+//===================================================================
+// Class providing delayed deletion of garbage objects. Objects get deleted only when 'deleteAll()'
+// is called, or the deleter itself is destroyed.
+
+class VlDeleter final {
+    // MEMBERS
+    // Queue of new objects that should be deleted
+    std::vector m_newGarbage VL_GUARDED_BY(m_mutex);
+    // Queue of objects currently being deleted (only for deleteAll())
+    std::vector m_toDelete VL_GUARDED_BY(m_deleteMutex);
+    mutable VerilatedMutex m_mutex;  // Mutex protecting the 'new garbage' queue
+    mutable VerilatedMutex m_deleteMutex;  // Mutex protecting the delete queue
+
+public:
+    // CONSTRUCTOR
+    VlDeleter() = default;
+    ~VlDeleter() { deleteAll(); }
+
+private:
+    VL_UNCOPYABLE(VlDeleter);
+
+public:
+    // METHODS
+    // Adds a new object to the 'new garbage' queue.
+    void put(VlClass* const objp) VL_MT_SAFE {
+        const VerilatedLockGuard lock{m_mutex};
+        m_newGarbage.push_back(objp);
+    }
+
+    // Deletes all queued garbage objects.
+    void deleteAll() VL_MT_SAFE;
+};
+
+//===================================================================
+// Base class for all verilated classes. Includes a reference counter, and a pointer to the deleter
+// object that should destroy it after the counter reaches 0. This allows for easy construction of
+// VlClassRefs from 'this'. Also declares a virtual constructor, so that the object can be deleted
+// using a base pointer.
+
+class VlClass VL_NOT_FINAL {
+    // TYPES
+    template 
+    friend class VlClassRef;  // Needed for access to the ref counter and deleter
+
+    // MEMBERS
+    std::atomic m_counter{0};  // Reference count for this object
+    VlDeleter* m_deleter = nullptr;  // The deleter that will delete this object
+
+    // METHODS
+    // Atomically increments the reference counter
+    void refCountInc() VL_MT_SAFE { ++m_counter; }
+    // Atomically decrements the reference counter. Assuming VlClassRef semantics are sound, it
+    // should never get called at m_counter == 0.
+    void refCountDec() VL_MT_SAFE {
+        if (!--m_counter) m_deleter->put(this);
+    }
+
+public:
+    // CONSTRUCTORS
+    VlClass() = default;
+    VlClass(const VlClass& copied) {}
+    virtual ~VlClass() {}
+};
+
+//===================================================================
+// Represents the null pointer. Used for setting VlClassRef to null instead of
+// via nullptr_t, to prevent the implicit conversion of 0 to nullptr.
+struct VlNull {
+    operator bool() const { return false; }
+};
+
 //===================================================================
 // Verilog class reference container
 // There are no multithreaded locks on this; the base variable must
 // be protected by other means
 
-#define VlClassRef std::shared_ptr
+template 
+class VlClassRef final {
+private:
+    // TYPES
+    template 
+    friend class VlClassRef;  // Needed for template copy/move assignments
+
+    // MEMBERS
+    T_Class* m_objp = nullptr;  // Object pointed to
+
+    // METHODS
+    // Increase reference counter with null check
+    void refCountInc() const VL_MT_SAFE {
+        if (m_objp) m_objp->refCountInc();
+    }
+    // Decrease reference counter with null check
+    void refCountDec() const VL_MT_SAFE {
+        if (m_objp) m_objp->refCountDec();
+    }
+
+public:
+    // CONSTRUCTORS
+    VlClassRef() = default;
+    // Init with nullptr
+    VlClassRef(VlNull){};
+    template 
+    VlClassRef(VlDeleter& deleter, T_Args&&... args)
+        : m_objp{new T_Class{std::forward(args)...}} {
+        m_objp->m_deleter = &deleter;
+        refCountInc();
+    }
+    // Explicit to avoid implicit conversion from 0
+    explicit VlClassRef(T_Class* objp)
+        : m_objp{objp} {
+        refCountInc();
+    }
+    // cppcheck-suppress noExplicitConstructor
+    VlClassRef(const VlClassRef& copied)
+        : m_objp{copied.m_objp} {
+        refCountInc();
+    }
+    // cppcheck-suppress noExplicitConstructor
+    VlClassRef(VlClassRef&& moved)
+        : m_objp{vlstd::exchange(moved.m_objp, nullptr)} {}
+    ~VlClassRef() { refCountDec(); }
+
+    // METHODS
+    // Copy and move assignments
+    VlClassRef& operator=(const VlClassRef& copied) {
+        refCountDec();
+        m_objp = copied.m_objp;
+        refCountInc();
+        return *this;
+    }
+    VlClassRef& operator=(VlClassRef&& moved) {
+        refCountDec();
+        m_objp = vlstd::exchange(moved.m_objp, nullptr);
+        return *this;
+    }
+    template 
+    VlClassRef& operator=(const VlClassRef& copied) {
+        refCountDec();
+        m_objp = copied.m_objp;
+        refCountInc();
+        return *this;
+    }
+    template 
+    VlClassRef& operator=(VlClassRef&& moved) {
+        refCountDec();
+        m_objp = vlstd::exchange(moved.m_objp, nullptr);
+        return *this;
+    }
+    // Assign with nullptr
+    VlClassRef& operator=(VlNull) {
+        refCountDec();
+        m_objp = nullptr;
+        return *this;
+    }
+    // Dynamic caster
+    template 
+    VlClassRef dynamicCast() const {
+        return VlClassRef{dynamic_cast(m_objp)};
+    }
+    // Dereference operators
+    T_Class& operator*() const { return *m_objp; }
+    T_Class* operator->() const { return m_objp; }
+    // For 'if (ptr)...'
+    operator bool() const { return m_objp; }
+};
+
+#define VL_NEW(Class, ...) \
+    VlClassRef { vlSymsp->__Vm_deleter, __VA_ARGS__ }
+
+#define VL_KEEP_THIS \
+    VlClassRef::type> __Vthisref { this }
 
 template   // T typically of type VlClassRef
 inline T VL_NULL_CHECK(T t, const char* filename, int linenum) {
@@ -923,7 +1186,7 @@ inline T VL_NULL_CHECK(T t, const char* filename, int linenum) {
 
 template 
 static inline bool VL_CAST_DYNAMIC(VlClassRef in, VlClassRef& outr) {
-    VlClassRef casted = std::dynamic_pointer_cast(in);
+    VlClassRef casted = in.template dynamicCast();
     if (VL_LIKELY(casted)) {
         outr = casted;
         return true;
diff --git a/include/verilated_vcd_sc.h b/include/verilated_vcd_sc.h
index c0ba6e528..ad8de7133 100644
--- a/include/verilated_vcd_sc.h
+++ b/include/verilated_vcd_sc.h
@@ -86,6 +86,7 @@ private:
 
     // clang-format off
     // Formatting matches that of sc_trace.h
+    // LCOV_EXCL_START
 #if (SYSTEMC_VERSION >= 20171012)
     DECL_TRACE_METHOD_A( sc_event )
     DECL_TRACE_METHOD_A( sc_time )
@@ -120,6 +121,7 @@ private:
 
     DECL_TRACE_METHOD_A( sc_dt::sc_bv_base )
     DECL_TRACE_METHOD_A( sc_dt::sc_lv_base )
+    // LCOV_EXCL_STOP
     // clang-format on
 
 #undef DECL_TRACE_METHOD_A
diff --git a/include/verilatedos.h b/include/verilatedos.h
index c5677b992..de89e822e 100644
--- a/include/verilatedos.h
+++ b/include/verilatedos.h
@@ -69,9 +69,9 @@
 #  define VL_EXCLUDES(x) __attribute__((locks_excluded(x)))
 #  define VL_SCOPED_CAPABILITY __attribute__((scoped_lockable))
 # endif
-# define VL_LIKELY(x) __builtin_expect(!!(x), 1)
-# define VL_UNLIKELY(x) __builtin_expect(!!(x), 0)
-# define VL_UNREACHABLE __builtin_unreachable();
+# define VL_LIKELY(x) __builtin_expect(!!(x), 1)  // Prefer over C++20 [[likely]]
+# define VL_UNLIKELY(x) __builtin_expect(!!(x), 0)  // Prefer over C++20 [[unlikely]]
+# define VL_UNREACHABLE __builtin_unreachable()  // C++23 std::unreachable()
 # define VL_PREFETCH_RD(p) __builtin_prefetch((p), 0)
 # define VL_PREFETCH_RW(p) __builtin_prefetch((p), 1)
 #endif
@@ -163,14 +163,22 @@
 // Comment tag that Function is pure (and thus also VL_MT_SAFE)
 #define VL_PURE
 // Comment tag that function is threadsafe when VL_THREADED
-#define VL_MT_SAFE
+#if defined(__clang__)
+# define VL_MT_SAFE __attribute__((annotate("MT_SAFE")))
+#else
+# define VL_MT_SAFE
+#endif
 // Comment tag that function is threadsafe when VL_THREADED, only
 // during normal operation (post-init)
 #define VL_MT_SAFE_POSTINIT
 // Attribute that function is clang threadsafe and uses given mutex
 #define VL_MT_SAFE_EXCLUDES(mutex) VL_EXCLUDES(mutex)
 // Comment tag that function is not threadsafe when VL_THREADED
-#define VL_MT_UNSAFE
+#if defined(__clang__)
+# define VL_MT_UNSAFE __attribute__((annotate("MT_UNSAFE")))
+#else
+# define VL_MT_UNSAFE
+#endif
 // Comment tag that function is not threadsafe when VL_THREADED,
 // protected to make sure single-caller
 #define VL_MT_UNSAFE_ONE
@@ -540,6 +548,8 @@ using ssize_t = uint32_t;  ///< signed size_t; returned from read()
 //=========================================================================
 // Conversions
 
+#include 
+
 namespace vlstd {
 
 template 
@@ -564,6 +574,14 @@ T const& as_const(T& v) {
     return v;
 }
 
+// C++14's std::exchange
+template 
+T exchange(T& obj, U&& new_value) {
+    T old_value = std::move(obj);
+    obj = std::forward(new_value);
+    return old_value;
+}
+
 };  // namespace vlstd
 
 //=========================================================================
diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in
index 346521767..bcde753fc 100644
--- a/src/Makefile_obj.in
+++ b/src/Makefile_obj.in
@@ -166,7 +166,6 @@ RAW_OBJS = \
 	V3Case.o \
 	V3Cast.o \
 	V3Cdc.o \
-	V3Changed.o \
 	V3Class.o \
 	V3Clean.o \
 	V3Clock.o \
@@ -181,7 +180,15 @@ RAW_OBJS = \
 	V3Depth.o \
 	V3DepthBlock.o \
 	V3Descope.o \
+	V3Dfg.o \
+	V3DfgAstToDfg.o \
+	V3DfgDecomposition.o \
+	V3DfgDfgToAst.o \
+	V3DfgOptimizer.o \
+	V3DfgPasses.o \
+	V3DfgPeephole.o \
 	V3DupFinder.o \
+	V3Timing.o \
 	V3EmitCBase.o \
 	V3EmitCConstPool.o \
 	V3EmitCFunc.o \
@@ -201,7 +208,6 @@ RAW_OBJS = \
 	V3FileLine.o \
 	V3Force.o \
 	V3Gate.o \
-	V3GenClk.o \
 	V3Global.o \
 	V3Graph.o \
 	V3GraphAlg.o \
@@ -239,6 +245,11 @@ RAW_OBJS = \
 	V3ProtectLib.o \
 	V3Randomize.o \
 	V3Reloop.o \
+	V3Sched.o \
+	V3SchedAcyclic.o \
+	V3SchedPartition.o \
+	V3SchedReplicate.o \
+	V3SchedTiming.o \
 	V3Scope.o \
 	V3Scoreboard.o \
 	V3Slice.o \
@@ -279,6 +290,7 @@ NON_STANDALONE_HEADERS = \
 	V3AstNodeDType.h  \
 	V3AstNodeMath.h  \
 	V3AstNodeOther.h  \
+	V3DfgVertices.h  \
 	V3WidthCommit.h  \
 
 AST_DEFS := \
@@ -286,10 +298,19 @@ AST_DEFS := \
   V3AstNodeMath.h \
   V3AstNodeOther.h \
 
+DFG_DEFS := \
+  V3DfgVertices.h
+
+#### astgen common flags
+
+ASTGENFLAGS = -I $(srcdir)
+ASTGENFLAGS += $(foreach f,$(AST_DEFS),--astdef $f)
+ASTGENFLAGS += $(foreach f,$(DFG_DEFS),--dfgdef $f)
+
 #### Linking
 
 ifeq ($(VL_VLCOV),)
-PREDEP_H = V3Ast__gen_classes.h
+PREDEP_H = V3Ast__gen_forward_class_decls.h
 OBJS += $(RAW_OBJS) $(NC_OBJS)
 else
 PREDEP_H =
@@ -308,8 +329,8 @@ V3Number_test: V3Number_test.o
 
 #### Modules
 
-%__gen.cpp:	%.cpp $(ASTGEN) $(AST_DEFS)
-	$(PYTHON3) $(ASTGEN) -I $(srcdir) $(foreach f,$(AST_DEFS),--astdef $f) $*.cpp
+%__gen.cpp:	%.cpp $(ASTGEN) $(AST_DEFS) $(DFG_DEFS)
+	$(PYTHON3) $(ASTGEN) $(ASTGENFLAGS) $*.cpp
 
 %.o:	%.cpp
 	$(OBJCACHE) ${CXX} ${CXXFLAGS} ${CPPFLAGSWALL} -c $< -o $@
@@ -331,7 +352,7 @@ V3PreProc.o:	V3PreProc.cpp V3PreLex.yy.cpp
 #### Generated files
 
 # Target rule called before parallel build to make generated files
-serial:: V3Ast__gen_classes.h V3ParseBison.c
+serial:: V3Ast__gen_forward_class_decls.h V3ParseBison.c
 
 serial_vlcov:: vlcovgen.d
 
@@ -339,8 +360,8 @@ vlcovgen.d: $(VLCOVGEN) $(srcdir)/../include/verilated_cov_key.h
 	$(PYTHON3) $(VLCOVGEN) --srcdir $(srcdir)
 	touch $@
 
-V3Ast__gen_classes.h : $(ASTGEN) $(AST_DEFS)
-	$(PYTHON3) $(ASTGEN) -I $(srcdir) $(foreach f,$(AST_DEFS),--astdef $f) --classes
+V3Ast__gen_forward_class_decls.h: $(ASTGEN) $(AST_DEFS) $(DFG_DEFS)
+	$(PYTHON3) $(ASTGEN) $(ASTGENFLAGS) --classes
 
 V3ParseBison.h: V3ParseBison.c
 
diff --git a/src/V3Active.cpp b/src/V3Active.cpp
index ac8080eb1..0350609cc 100644
--- a/src/V3Active.cpp
+++ b/src/V3Active.cpp
@@ -129,7 +129,7 @@ public:
         // Start a new if/else tracking graph
         // See NODE STATE comment in ActiveLatchCheckVisitor
         AstNode::user1ClearTree();
-        m_curVertexp = new LatchDetectGraphVertex(this, "ROOT");
+        m_curVertexp = new LatchDetectGraphVertex{this, "ROOT"};
     }
     // Clear out userp field of referenced outputs on destruction
     // (occurs at the end of each combinational always block)
@@ -165,7 +165,7 @@ public:
         } else {
             outVertexp = castVertexp(nodep->varp()->user1p());
         }
-        new V3GraphEdge(this, m_curVertexp, outVertexp, 1);
+        new V3GraphEdge{this, m_curVertexp, outVertexp, 1};
     }
     // Run latchCheckInternal on each variable assigned by the always block to see if all control
     // paths make an assignment. Detected latches are flagged in the variables AstVar
@@ -202,8 +202,10 @@ class ActiveNamer final : public VNVisitor {
 private:
     // STATE
     AstScope* m_scopep = nullptr;  // Current scope to add statement to
-    AstActive* m_iActivep = nullptr;  // For current scope, the IActive we're building
-    AstActive* m_cActivep = nullptr;  // For current scope, the SActive(combo) we're building
+    AstActive* m_sActivep = nullptr;  // For current scope, the Static active we're building
+    AstActive* m_iActivep = nullptr;  // For current scope, the Initial active we're building
+    AstActive* m_fActivep = nullptr;  // For current scope, the Final active we're building
+    AstActive* m_cActivep = nullptr;  // For current scope, the Combo active we're building
 
     // Map from AstSenTree (equivalence) to the corresponding AstActive created.
     std::unordered_map, AstActive*> m_activeMap;
@@ -213,10 +215,13 @@ private:
         UASSERT_OBJ(m_scopep, nodep, "nullptr scope");
         m_scopep->addBlocksp(nodep);
     }
+
     // VISITORS
     void visit(AstScope* nodep) override {
         m_scopep = nodep;
+        m_sActivep = nullptr;
         m_iActivep = nullptr;
+        m_fActivep = nullptr;
         m_cActivep = nullptr;
         m_activeMap.clear();
         iterateChildren(nodep);
@@ -230,30 +235,35 @@ private:
     void visit(AstNodeStmt*) override {}  // Accelerate
     void visit(AstNode* nodep) override { iterateChildren(nodep); }
 
+    // Specialized below for the special sensitivity classes
+    template 
+    AstActive*& getSpecialActive();
+
 public:
     // METHODS
     AstScope* scopep() { return m_scopep; }
-    AstActive* getCActive(FileLine* fl) {
-        if (!m_cActivep) {
-            m_cActivep = new AstActive(
-                fl, "combo", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Combo())));
-            m_cActivep->sensesStorep(m_cActivep->sensesp());
-            addActive(m_cActivep);
-        }
-        return m_cActivep;
+
+    // Make a new AstActive sensitive to the given special sensitivity class and return it
+    template 
+    AstActive* makeSpecialActive(FileLine* const fl) {
+        AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}};
+        auto* const activep = new AstActive{fl, "", senTreep};
+        activep->sensesStorep(activep->sensesp());
+        addActive(activep);
+        return activep;
     }
-    AstActive* getIActive(FileLine* fl) {
-        if (!m_iActivep) {
-            m_iActivep = new AstActive(
-                fl, "initial", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Initial())));
-            m_iActivep->sensesStorep(m_iActivep->sensesp());
-            addActive(m_iActivep);
-        }
-        return m_iActivep;
+
+    // Return an AstActive sensitive to the given special sensitivity class (possibly pre-created)
+    template 
+    AstActive* getSpecialActive(FileLine* fl) {
+        AstActive*& cachep = getSpecialActive();
+        if (!cachep) cachep = makeSpecialActive(fl);
+        return cachep;
     }
 
     // Return an AstActive that is sensitive to a SenTree equivalent to the given sentreep.
     AstActive* getActive(FileLine* fl, AstSenTree* sensesp) {
+        UASSERT(sensesp, "Must be non-null");
 
         auto it = m_activeMap.find(*sensesp);
         // If found matching AstActive, return it
@@ -261,7 +271,7 @@ public:
 
         // No such AstActive yet, creat it, and add to map.
         AstSenTree* const newsenp = sensesp->cloneTree(false);
-        AstActive* const activep = new AstActive(fl, "sequent", newsenp);
+        AstActive* const activep = new AstActive{fl, "sequent", newsenp};
         activep->sensesStorep(activep->sensesp());
         addActive(activep);
         m_activeMap.emplace(*newsenp, activep);
@@ -274,6 +284,23 @@ public:
     void main(AstScope* nodep) { iterate(nodep); }
 };
 
+template <>
+AstActive*& ActiveNamer::getSpecialActive() {
+    return m_sActivep;
+}
+template <>
+AstActive*& ActiveNamer::getSpecialActive() {
+    return m_iActivep;
+}
+template <>
+AstActive*& ActiveNamer::getSpecialActive() {
+    return m_fActivep;
+}
+template <>
+AstActive*& ActiveNamer::getSpecialActive() {
+    return m_cActivep;
+}
+
 //######################################################################
 // Latch checking visitor
 
@@ -308,10 +335,10 @@ private:
 
 public:
     // CONSTRUCTORS
-    ActiveLatchCheckVisitor(AstNode* nodep, VAlwaysKwd kwd) {
+    ActiveLatchCheckVisitor(AstNode* nodep, bool expectLatch) {
         m_graph.begin();
         iterate(nodep);
-        m_graph.latchCheck(nodep, kwd == VAlwaysKwd::ALWAYS_LATCH);
+        m_graph.latchCheck(nodep, expectLatch);
     }
     ~ActiveLatchCheckVisitor() override = default;
 };
@@ -346,9 +373,11 @@ private:
         }
 
         // Convert to blocking assignment
-        nodep->replaceWith(new AstAssign{nodep->fileline(),  //
-                                         nodep->lhsp()->unlinkFrBack(),  //
-                                         nodep->rhsp()->unlinkFrBack()});
+        nodep->replaceWith(new AstAssign{
+            nodep->fileline(),  //
+            nodep->lhsp()->unlinkFrBack(),  //
+            nodep->rhsp()->unlinkFrBack(),  //
+            nodep->timingControlp() ? nodep->timingControlp()->unlinkFrBack() : nullptr});
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
 
@@ -356,7 +385,7 @@ private:
         // Blocking assignments are always OK in combinational (and initial/final) processes
         if (m_check != CT_SEQ) return;
 
-        const bool ignore = nodep->lhsp()->forall([&](const AstVarRef* refp) {
+        const bool ignore = nodep->lhsp()->forall([&](const AstVarRef* refp) {
             // Ignore reads (e.g.: index expressions)
             if (refp->access().isReadOnly()) return true;
             const AstVar* const varp = refp->varp();
@@ -393,87 +422,25 @@ class ActiveVisitor final : public VNVisitor {
 private:
     // NODE STATE
     //  Each call to V3Const::constify
+    //   AstVarScope::user1()           bool: This VarScope is referenced in the sensitivity list
+    //   AstVarScope::user2()           bool: This VarScope is written in the current process
     //   AstNode::user4()               Used by V3Const::constify, called below
 
     // STATE
     ActiveNamer m_namer;  // Tracking of active names
-    AstCFunc* m_scopeFinalp = nullptr;  // Final function for this scope
-    bool m_itemCombo = false;  // Found a SenItem combo
-    bool m_itemSequent = false;  // Found a SenItem sequential
-
-    // VISITORS
-    void visit(AstScope* nodep) override {
-        // Create required actives and add to scope
-        UINFO(4, " SCOPE   " << nodep << endl);
-        // Clear last scope's names, and collect this scope's existing names
-        m_namer.main(nodep);
-        m_scopeFinalp = nullptr;
-        iterateChildren(nodep);
-    }
-    void visit(AstActive* nodep) override {
-        // Actives are being formed, so we can ignore any already made
-    }
-    void visit(AstInitialStatic* nodep) override {
-        // Relink to IACTIVE, unless already under it
-        UINFO(4, "    INITIAL " << nodep << endl);
-        const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
-        AstActive* const wantactivep = m_namer.getIActive(nodep->fileline());
-        nodep->unlinkFrBack();
-        wantactivep->addStmtsp(nodep);
-    }
-    void visit(AstInitial* nodep) override {
-        // Relink to IACTIVE, unless already under it
-        UINFO(4, "    INITIAL " << nodep << endl);
-        const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
-        AstActive* const wantactivep = m_namer.getIActive(nodep->fileline());
-        nodep->unlinkFrBack();
-        wantactivep->addStmtsp(nodep);
-    }
-    void visit(AstAssignAlias* nodep) override {
-        // Relink to CACTIVE, unless already under it
-        UINFO(4, "    ASSIGNW " << nodep << endl);
-        AstActive* const wantactivep = m_namer.getCActive(nodep->fileline());
-        nodep->unlinkFrBack();
-        wantactivep->addStmtsp(nodep);
-    }
-    void visit(AstAssignW* nodep) override {
-        // Relink to CACTIVE, unless already under it
-        UINFO(4, "    ASSIGNW " << nodep << endl);
-        AstActive* const wantactivep = m_namer.getCActive(nodep->fileline());
-        nodep->unlinkFrBack();
-        wantactivep->addStmtsp(nodep);
-    }
-    void visit(AstCoverToggle* nodep) override {
-        // Relink to CACTIVE, unless already under it
-        UINFO(4, "    COVERTOGGLE " << nodep << endl);
-        AstActive* const wantactivep = m_namer.getCActive(nodep->fileline());
-        nodep->unlinkFrBack();
-        wantactivep->addStmtsp(nodep);
-    }
-    void visit(AstFinal* nodep) override {
-        // Relink to CFUNC for the final
-        UINFO(4, "    FINAL " << nodep << endl);
-        if (!nodep->stmtsp()) {  // Empty, Kill it.
-            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
-            return;
-        }
-        const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
-        if (!m_scopeFinalp) {
-            m_scopeFinalp = new AstCFunc(
-                nodep->fileline(), "_final_" + m_namer.scopep()->nameDotless(), m_namer.scopep());
-            m_scopeFinalp->dontCombine(true);
-            m_scopeFinalp->isFinal(true);
-            m_scopeFinalp->isStatic(false);
-            m_scopeFinalp->isLoose(true);
-            m_scopeFinalp->slow(true);
-            m_namer.scopep()->addBlocksp(m_scopeFinalp);
-        }
-        nodep->unlinkFrBack();
-        m_scopeFinalp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext());
-        VL_DO_DANGLING(nodep->deleteTree(), nodep);
-    }
+    bool m_clockedProcess = false;  // Whether current process is a clocked process
+    bool m_allChanged = false;  // Whether all SenItem in the SenTree are ET_CHANGED
+    bool m_walkingBody = false;  // Walking body of a process
+    bool m_canBeComb = false;  // Whether current clocked process can be turned into a comb process
 
     // METHODS
+    template 
+    void moveUnderSpecial(AstNode* nodep) {
+        AstActive* const wantactivep = m_namer.getSpecialActive(nodep->fileline());
+        nodep->unlinkFrBack();
+        wantactivep->addStmtsp(nodep);
+    }
+
     void visitAlways(AstNode* nodep, AstSenTree* oldsensesp, VAlwaysKwd kwd) {
         // Move always to appropriate ACTIVE based on its sense list
         if (oldsensesp && oldsensesp->sensesp() && oldsensesp->sensesp()->isNever()) {
@@ -484,112 +451,173 @@ private:
             return;
         }
 
-        // Read sensitivities
-        m_itemCombo = false;
-        m_itemSequent = false;
-        iterateAndNextNull(oldsensesp);
-        bool combo = m_itemCombo;
-        bool sequent = m_itemSequent;
+        {
+            const VNUser1InUse user1InUse;
 
-        if (!combo && !sequent) combo = true;  // If no list, Verilog 2000: always @ (*)
-        if (combo && sequent) {
-            nodep->v3warn(E_UNSUPPORTED, "Unsupported: Mixed edge (pos/negedge) and activity "
-                                         "(no edge) sensitive activity list");
-            sequent = false;
-        }
-
-        AstActive* wantactivep = nullptr;
-        if (combo && !sequent) {
-            // Combo:  Relink to ACTIVE(combo)
-            wantactivep = m_namer.getCActive(nodep->fileline());
-        } else {
-            // Sequential: Build a ACTIVE(name)
-            // OPTIMIZE: We could substitute a constant for things in the sense list, for example
-            // always (posedge RESET) { if (RESET).... }  we know RESET is true.
-            // Summarize a long list of combo inputs as just "combo"
-#ifndef __COVERITY__  // Else dead code on next line.
-            if (combo) {
-                oldsensesp->addSensesp(new AstSenItem(nodep->fileline(), AstSenItem::Combo()));
+            // Walk sensitivity list
+            m_clockedProcess = false;
+            m_allChanged = true;
+            if (oldsensesp) {
+                oldsensesp->unlinkFrBack();
+                iterateChildrenConst(oldsensesp);
+            }
+
+            // If all SenItems are ET_CHANGE, then walk the body to determine if this process
+            // could be turned into a combinational process instead.
+            if (m_allChanged) {
+                const VNUser2InUse user2InUse;
+                m_walkingBody = true;
+                m_canBeComb = true;
+                iterateChildrenConst(nodep);
+                m_walkingBody = false;
+                if (m_canBeComb) m_clockedProcess = false;
             }
-#endif
-            wantactivep = m_namer.getActive(nodep->fileline(), oldsensesp);
         }
 
+        AstActive* const wantactivep
+            = !m_clockedProcess ? m_namer.getSpecialActive(nodep->fileline())
+              : oldsensesp      ? m_namer.getActive(nodep->fileline(), oldsensesp)
+                                : m_namer.getSpecialActive(nodep->fileline());
+
         // Delete sensitivity list
-        if (oldsensesp) {
-            VL_DO_DANGLING(oldsensesp->unlinkFrBackWithNext()->deleteTree(), oldsensesp);
-        }
+        if (oldsensesp) VL_DO_DANGLING(oldsensesp->deleteTree(), oldsensesp);
 
         // Move node to new active
         nodep->unlinkFrBack();
         wantactivep->addStmtsp(nodep);
 
-        // Warn and/or convert any delayed assignments
-        if (combo && !sequent) {
-            { ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB}; }
-            const ActiveLatchCheckVisitor latchvisitor{nodep, kwd};
-        } else if (!combo && sequent) {
-            ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_SEQ};
+        // Warn and convert any delayed assignments
+        {
+            ActiveDlyVisitor{nodep, m_clockedProcess ? ActiveDlyVisitor::CT_SEQ
+                                                     : ActiveDlyVisitor::CT_COMB};
+        }
+
+        // check combinational processes for latches
+        if (!m_clockedProcess || kwd == VAlwaysKwd::ALWAYS_LATCH) {
+            const ActiveLatchCheckVisitor latchvisitor{nodep, kwd == VAlwaysKwd::ALWAYS_LATCH};
         }
     }
-    void visit(AstAlways* nodep) override {
-        // Move always to appropriate ACTIVE based on its sense list
-        UINFO(4, "    ALW   " << nodep << endl);
-        // if (debug() >= 9) nodep->dumpTree(cout, "  Alw: ");
 
-        if (!nodep->stmtsp()) {
-            // Empty always.  Kill it.
+    void visitSenItems(AstNode* nodep) {
+        if (v3Global.opt.timing().isSetTrue()) {
+            nodep->foreach([this](AstSenItem* senItemp) { visit(senItemp); });
+        }
+    }
+
+    // VISITORS
+    void visit(AstScope* nodep) override {
+        m_namer.main(nodep);  // Clear last scope's names, and collect this scope's existing names
+        iterateChildren(nodep);
+    }
+    void visit(AstActive* nodep) override {
+        // Actives are being formed, so we can ignore any already made
+    }
+
+    void visit(AstInitialStatic* nodep) override { moveUnderSpecial(nodep); }
+    void visit(AstInitial* nodep) override {
+        const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
+        visitSenItems(nodep);
+        moveUnderSpecial(nodep);
+    }
+    void visit(AstFinal* nodep) override {
+        const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
+        moveUnderSpecial(nodep);
+    }
+    void visit(AstAssignAlias* nodep) override { moveUnderSpecial(nodep); }
+    void visit(AstCoverToggle* nodep) override { moveUnderSpecial(nodep); }
+    void visit(AstAssignW* nodep) override { moveUnderSpecial(nodep); }
+    void visit(AstAlways* nodep) override {
+        if (!nodep->stmtsp()) {  // Empty always. Remove it now.
             VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
             return;
         }
+        visitSenItems(nodep);
         visitAlways(nodep, nodep->sensesp(), nodep->keyword());
     }
     void visit(AstAlwaysPostponed* nodep) override {
-        UINFO(4, "    ALW   " << nodep << endl);
-        if (!nodep->stmtsp()) {
-            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
-            return;
-        }
-        visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS);
+        // Might be empty with later optimizations, so this assertion can be removed,
+        // but for now it is guaranteed to be not empty.
+        UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not be empty");
+        // Make a new active for it, needs to be the only item under the active for V3Sched
+        AstActive* const activep = m_namer.makeSpecialActive(nodep->fileline());
+        activep->addStmtsp(nodep->unlinkFrBack());
     }
     void visit(AstAlwaysPublic* nodep) override {
-        // Move always to appropriate ACTIVE based on its sense list
-        UINFO(4, "    ALWPub   " << nodep << endl);
-        // if (debug() >= 9) nodep->dumpTree(cout, "  Alw: ");
         visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS);
     }
+    void visit(AstCFunc* nodep) override { visitSenItems(nodep); }
     void visit(AstSenItem* nodep) override {
-        if (nodep->varrefp()) {
-            if (const AstBasicDType* const basicp = nodep->varrefp()->dtypep()->basicp()) {
-                if (basicp->isEventValue()) {
-                    // Events need to be treated as active high so we only activate on event being
-                    // 1
-                    UINFO(8, "Demote event to HIGHEDGE " << nodep << endl);
-                    nodep->edgeType(VEdgeType::ET_HIGHEDGE);
-                }
+        UASSERT_OBJ(!m_walkingBody, nodep,
+                    "Should not reach here when walking body without --timing");
+        if (!nodep->sensp()) return;  // Ignore sequential items (e.g.: initial, comb, etc.)
+
+        m_clockedProcess = true;
+        if (nodep->edgeType() != VEdgeType::ET_CHANGED) m_allChanged = false;
+
+        if (const auto* const dtypep = nodep->sensp()->dtypep()) {
+            if (const auto* const basicp = dtypep->basicp()) {
+                if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT);
             }
         }
-        if (nodep->edgeType() == VEdgeType::ET_ANYEDGE) {
-            m_itemCombo = true;
-            // Delete the sensitivity
-            // We'll add it as a generic COMBO SenItem in a moment.
-            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
-        } else if (nodep->varrefp()) {
-            // V3LinkResolve should have cleaned most of these up
-            if (!nodep->varrefp()->width1()) {
-                nodep->v3warn(E_UNSUPPORTED,
-                              "Unsupported: Non-single bit wide signal pos/negedge sensitivity: "
-                                  << nodep->varrefp()->prettyNameQ());
-            }
-            m_itemSequent = true;
-            nodep->varrefp()->varp()->usedClock(true);
+
+        nodep->sensp()->foreach([](const AstVarRef* refp) {
+            refp->varp()->usedClock(true);
+            refp->varScopep()->user1(true);
+        });
+    }
+
+    void visit(AstVarRef* nodep) override {
+        AstVarScope* const vscp = nodep->varScopep();
+        if (nodep->access().isWriteOnly()) {
+            vscp->user2(true);
+        } else {
+            // If the variable is read before it is written (and is not a never-changing value),
+            // and is not in the sensitivity list, then this cannot be optimized into a
+            // combinational process
+            // TODO: live variable analysis would be more precise
+            if (!vscp->user2() && !vscp->varp()->valuep() && !vscp->user1()) m_canBeComb = false;
         }
     }
+    void visit(AstAssignDly* nodep) override {
+        m_canBeComb = false;
+        if (nodep->isTimingControl()) m_clockedProcess = true;
+        iterateChildrenConst(nodep);
+    }
+    void visit(AstFireEvent* nodep) override {
+        m_canBeComb = false;
+        iterateChildrenConst(nodep);
+    }
+    void visit(AstAssignForce* nodep) override {
+        m_canBeComb = false;
+        iterateChildrenConst(nodep);
+    }
+    void visit(AstRelease* nodep) override {
+        m_canBeComb = false;
+        iterateChildrenConst(nodep);
+    }
+    void visit(AstFork* nodep) override {
+        if (nodep->isTimingControl()) {
+            m_canBeComb = false;
+            m_clockedProcess = true;
+        }
+        // Do not iterate children, technically not part of this process
+    }
 
     //--------------------
-    void visit(AstNodeMath*) override {}  // Accelerate
+    void visit(AstVar*) override {}  // Accelerate
     void visit(AstVarScope*) override {}  // Accelerate
-    void visit(AstNode* nodep) override { iterateChildren(nodep); }
+    void visit(AstNode* nodep) override {
+        if (!v3Global.opt.timing().isSetTrue() && m_walkingBody && !m_canBeComb) {
+            return;  // Accelerate
+        }
+        if (!nodep->isPure()) m_canBeComb = false;
+        if (nodep->isTimingControl()) {
+            m_canBeComb = false;
+            m_clockedProcess = true;
+            return;
+        }
+        iterateChildren(nodep);
+    }
 
 public:
     // CONSTRUCTORS
diff --git a/src/V3ActiveTop.cpp b/src/V3ActiveTop.cpp
index eefa36685..2be352c17 100644
--- a/src/V3ActiveTop.cpp
+++ b/src/V3ActiveTop.cpp
@@ -39,19 +39,27 @@ VL_DEFINE_DEBUG_FUNCTIONS;
 // Active class functions
 
 class ActiveTopVisitor final : public VNVisitor {
-private:
-    // NODE STATE
-    //  Entire netlist
-    //   AstNode::user()                bool. True if processed
-    //  Each call to V3Const::constify
-    //   AstNode::user4()               Used by V3Const::constify, called below
-    const VNUser1InUse m_inuser1;
-
     // STATE
     SenTreeFinder m_finder;  // Find global sentree's / add them under the AstTopScope
 
     // METHODS
 
+    static bool isInitial(AstNode* nodep) {
+        const VNUser1InUse user1InUse;
+        // Return true if no variables that read.
+        return nodep->forall([&](const AstVarRef* refp) -> bool {
+            AstVarScope* const vscp = refp->varScopep();
+            // Note: Use same heuristic as ordering does to ignore written variables
+            // TODO: Use live variable analysis.
+            if (refp->access().isWriteOnly()) {
+                vscp->user1(true);
+                return true;
+            }
+            // Read or ReadWrite: OK if written before
+            return vscp->user1();
+        });
+    }
+
     // VISITORS
     void visit(AstNodeModule* nodep) override {
         // Create required actives and add to module
@@ -72,15 +80,6 @@ private:
             VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
             return;
         }
-        // Copy combo tree to settlement tree with duplicated statements
-        if (sensesp->hasCombo()) {
-            AstSenTree* const newsentreep = new AstSenTree(
-                nodep->fileline(), new AstSenItem(nodep->fileline(), AstSenItem::Settle()));
-            AstActive* const newp = new AstActive(nodep->fileline(), "settle", newsentreep);
-            newp->sensesStorep(newsentreep);
-            if (nodep->stmtsp()) newp->addStmtsp(nodep->stmtsp()->cloneTree(true));
-            nodep->addNextHere(newp);
-        }
         // Move the SENTREE for each active up to the global level.
         // This way we'll easily see what clock domains are identical
         AstSenTree* const wantp = m_finder.getSenTree(sensesp);
@@ -99,8 +98,23 @@ private:
             }
             nodep->sensesp(wantp);
         }
-        // No need to do statements under it, they're already moved.
-        // iterateChildren(nodep);
+
+        // If this is combinational logic that does not read any variables, then it really is an
+        // initial block in disguise, so move such logic under an Initial AstActive, V3Order would
+        // prune these otherwise.
+        // TODO: we should warn for these if they were 'always @*' as some (including strictly
+        //       compliant) simulators will never execute these.
+        if (nodep->sensesp()->hasCombo()) {
+            FileLine* const flp = nodep->fileline();
+            AstActive* initialp = nullptr;
+            for (AstNode *logicp = nodep->stmtsp(), *nextp; logicp; logicp = nextp) {
+                nextp = logicp->nextp();
+                if (!isInitial(logicp)) continue;
+                if (!initialp) initialp = new AstActive{flp, "", m_finder.getInitial()};
+                initialp->addStmtsp(logicp->unlinkFrBack());
+            }
+            if (initialp) nodep->addHereThisAsNext(initialp);
+        }
     }
     void visit(AstNodeProcedure* nodep) override {  // LCOV_EXCL_LINE
         nodep->v3fatalSrc("Node should have been under ACTIVE");
diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp
index c15cd269d..40d9f609f 100644
--- a/src/V3Assert.cpp
+++ b/src/V3Assert.cpp
@@ -47,6 +47,7 @@ private:
     VDouble0 m_statAsNotImm;  // Statistic tracking
     VDouble0 m_statAsImm;  // Statistic tracking
     VDouble0 m_statAsFull;  // Statistic tracking
+    bool m_inSampled = false;  // True inside a sampled expression
 
     // METHODS
     string assertDisplayMessage(AstNode* nodep, const string& prefix, const string& message) {
@@ -58,7 +59,7 @@ private:
         nodep->displayType(VDisplayType::DT_WRITE);
         nodep->fmtp()->text(assertDisplayMessage(nodep, prefix, nodep->fmtp()->text()));
         // cppcheck-suppress nullPointer
-        AstNode* const timenewp = new AstTime(nodep->fileline(), m_modp->timeunit());
+        AstNode* const timenewp = new AstTime{nodep->fileline(), m_modp->timeunit()};
         if (AstNode* const timesp = nodep->fmtp()->exprsp()) {
             timesp->unlinkFrBackWithNext();
             timenewp->addNext(timesp);
@@ -68,13 +69,18 @@ private:
             nodep->fmtp()->scopeNamep(new AstScopeName{nodep->fileline(), true});
         }
     }
+    AstSampled* newSampledExpr(AstNode* nodep) {
+        const auto sampledp = new AstSampled{nodep->fileline(), nodep};
+        sampledp->dtypeFrom(nodep);
+        return sampledp;
+    }
     AstVarRef* newMonitorNumVarRefp(AstNode* nodep, VAccess access) {
         if (!m_monitorNumVarp) {
             m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum",
                                           nodep->findUInt64DType()};
             v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorNumVarp);
         }
-        const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorNumVarp, access);
+        const auto varrefp = new AstVarRef{nodep->fileline(), m_monitorNumVarp, access};
         varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
         return varrefp;
     }
@@ -84,7 +90,7 @@ private:
                                           nodep->findBitDType()};
             v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorOffVarp);
         }
-        const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorOffVarp, access);
+        const auto varrefp = new AstVarRef{nodep->fileline(), m_monitorOffVarp, access};
         varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
         return varrefp;
     }
@@ -92,16 +98,17 @@ private:
         // Add a internal if to check assertions are on.
         // Don't make this a AND term, as it's unlikely to need to test this.
         FileLine* const fl = nodep->fileline();
-        AstNode* const newp = new AstIf(
+        AstNodeIf* const newp = new AstIf{
             fl,
-            (force ? new AstConst(fl, AstConst::BitTrue())
+            (force ? new AstConst{fl, AstConst::BitTrue{}}
                    :  // If assertions are off, have constant propagation rip them out later
                  // This allows syntax errors and such to be detected normally.
                  (v3Global.opt.assertOn()
                       ? static_cast(
-                          new AstCMath(fl, "vlSymsp->_vm_contextp__->assertOn()", 1))
-                      : static_cast(new AstConst(fl, AstConst::BitFalse())))),
-            nodep);
+                          new AstCMath{fl, "vlSymsp->_vm_contextp__->assertOn()", 1})
+                      : static_cast(new AstConst{fl, AstConst::BitFalse{}}))),
+            nodep};
+        newp->isBoundsCheck(true);  // To avoid LATCH warning
         newp->user1(true);  // Don't assert/cover this if
         return newp;
     }
@@ -109,11 +116,11 @@ private:
     AstNode* newFireAssertUnchecked(AstNode* nodep, const string& message) {
         // Like newFireAssert() but omits the asserts-on check
         AstDisplay* const dispp
-            = new AstDisplay(nodep->fileline(), VDisplayType::DT_ERROR, message, nullptr, nullptr);
+            = new AstDisplay{nodep->fileline(), VDisplayType::DT_ERROR, message, nullptr, nullptr};
         dispp->fmtp()->timeunit(m_modp->timeunit());
         AstNode* const bodysp = dispp;
         replaceDisplay(dispp, "%%Error");  // Convert to standard DISPLAY format
-        bodysp->addNext(new AstStop(nodep->fileline(), true));
+        bodysp->addNext(new AstStop{nodep->fileline(), true});
         return bodysp;
     }
 
@@ -157,7 +164,8 @@ private:
             }
 
             if (bodysp && passsp) bodysp = bodysp->addNext(passsp);
-            ifp = new AstIf(nodep->fileline(), propp, bodysp);
+            ifp = new AstIf{nodep->fileline(), propp, bodysp};
+            ifp->isBoundsCheck(true);  // To avoid LATCH warning
             bodysp = ifp;
         } else if (VN_IS(nodep, Assert) || VN_IS(nodep, AssertIntrinsic)) {
             if (nodep->immediate()) {
@@ -169,7 +177,8 @@ private:
             if (passsp) passsp = newIfAssertOn(passsp, force);
             if (failsp) failsp = newIfAssertOn(failsp, force);
             if (!failsp) failsp = newFireAssertUnchecked(nodep, "'assert' failed.");
-            ifp = new AstIf(nodep->fileline(), propp, passsp, failsp);
+            ifp = new AstIf{nodep->fileline(), propp, passsp, failsp};
+            ifp->isBoundsCheck(true);  // To avoid LATCH warning
             // It's more LIKELY that we'll take the nullptr if clause
             // than the sim-killing else clause:
             ifp->branchPred(VBranchPred::BP_LIKELY);
@@ -180,7 +189,7 @@ private:
 
         AstNode* newp;
         if (sentreep) {
-            newp = new AstAlways(nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, bodysp);
+            newp = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, bodysp};
         } else {
             newp = bodysp;
         }
@@ -221,7 +230,7 @@ private:
                 // Build a bitmask of the true predicates
                 AstNode* const predp = ifp->condp()->cloneTree(false);
                 if (propp) {
-                    propp = new AstConcat(nodep->fileline(), predp, propp);
+                    propp = new AstConcat{nodep->fileline(), predp, propp};
                 } else {
                     propp = predp;
                 }
@@ -236,17 +245,18 @@ private:
             const bool allow_none = nodep->unique0Pragma();
 
             // Empty case means no property
-            if (!propp) propp = new AstConst(nodep->fileline(), AstConst::BitFalse());
+            if (!propp) propp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
 
             // Note: if this ends with an 'else', then we don't need to validate that one of the
             // predicates evaluates to true.
             AstNode* const ohot
                 = ((allow_none || hasDefaultElse)
-                       ? static_cast(new AstOneHot0(nodep->fileline(), propp))
-                       : static_cast(new AstOneHot(nodep->fileline(), propp)));
+                       ? static_cast(new AstOneHot0{nodep->fileline(), propp})
+                       : static_cast(new AstOneHot{nodep->fileline(), propp}));
             AstIf* const checkifp
-                = new AstIf(nodep->fileline(), new AstLogNot(nodep->fileline(), ohot),
-                            newFireAssert(nodep, "'unique if' statement violated"), newifp);
+                = new AstIf{nodep->fileline(), new AstLogNot{nodep->fileline(), ohot},
+                            newFireAssert(nodep, "'unique if' statement violated"), newifp};
+            checkifp->isBoundsCheck(true);  // To avoid LATCH warning
             checkifp->branchPred(VBranchPred::BP_UNLIKELY);
             nodep->replaceWith(checkifp);
             pushDeletep(nodep);
@@ -268,9 +278,9 @@ private:
                 // Need to add a default if there isn't one already
                 ++m_statAsFull;
                 if (!has_default) {
-                    nodep->addItemsp(new AstCaseItem(
+                    nodep->addItemsp(new AstCaseItem{
                         nodep->fileline(), nullptr /*DEFAULT*/,
-                        newFireAssert(nodep, "synthesis full_case, but non-match found")));
+                        newFireAssert(nodep, "synthesis full_case, but non-match found")});
                 }
             }
             if (nodep->parallelPragma() || nodep->uniquePragma() || nodep->unique0Pragma()) {
@@ -299,24 +309,25 @@ private:
                                                        icondp->cloneTree(false));
                             }
                             if (propp) {
-                                propp = new AstConcat(icondp->fileline(), onep, propp);
+                                propp = new AstConcat{icondp->fileline(), onep, propp};
                             } else {
                                 propp = onep;
                             }
                         }
                     }
                     // Empty case means no property
-                    if (!propp) propp = new AstConst(nodep->fileline(), AstConst::BitFalse());
+                    if (!propp) propp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
 
                     const bool allow_none = has_default || nodep->unique0Pragma();
                     AstNode* const ohot
                         = (allow_none
-                               ? static_cast(new AstOneHot0(nodep->fileline(), propp))
-                               : static_cast(new AstOneHot(nodep->fileline(), propp)));
-                    AstIf* const ifp = new AstIf(
-                        nodep->fileline(), new AstLogNot(nodep->fileline(), ohot),
+                               ? static_cast(new AstOneHot0{nodep->fileline(), propp})
+                               : static_cast(new AstOneHot{nodep->fileline(), propp}));
+                    AstIf* const ifp = new AstIf{
+                        nodep->fileline(), new AstLogNot{nodep->fileline(), ohot},
                         newFireAssert(nodep,
-                                      "synthesis parallel_case, but multiple matches found"));
+                                      "synthesis parallel_case, but multiple matches found")};
+                    ifp->isBoundsCheck(true);  // To avoid LATCH warning
                     ifp->branchPred(VBranchPred::BP_UNLIKELY);
                     nodep->addNotParallelp(ifp);
                 }
@@ -334,31 +345,63 @@ private:
             ticks = VN_AS(nodep->ticksp(), Const)->toUInt();
         }
         UASSERT_OBJ(ticks >= 1, nodep, "0 tick should have been checked in V3Width");
-        AstNode* inp = nodep->exprp()->unlinkFrBack();
+        AstNode* const exprp = nodep->exprp()->unlinkFrBack();
+        AstNode* inp = newSampledExpr(exprp);
         AstVar* invarp = nullptr;
         AstSenTree* const sentreep = nodep->sentreep();
         sentreep->unlinkFrBack();
         AstAlways* const alwaysp
-            = new AstAlways(nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, nullptr);
+            = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, nullptr};
         m_modp->addStmtsp(alwaysp);
         for (uint32_t i = 0; i < ticks; ++i) {
-            AstVar* const outvarp = new AstVar(
+            AstVar* const outvarp = new AstVar{
                 nodep->fileline(), VVarType::MODULETEMP,
-                "_Vpast_" + cvtToStr(m_modPastNum++) + "_" + cvtToStr(i), inp->dtypep());
+                "_Vpast_" + cvtToStr(m_modPastNum++) + "_" + cvtToStr(i), inp->dtypep()};
             m_modp->addStmtsp(outvarp);
-            AstNode* const assp = new AstAssignDly(
-                nodep->fileline(), new AstVarRef(nodep->fileline(), outvarp, VAccess::WRITE), inp);
+            AstNode* const assp = new AstAssignDly{
+                nodep->fileline(), new AstVarRef{nodep->fileline(), outvarp, VAccess::WRITE}, inp};
             alwaysp->addStmtsp(assp);
             // if (debug() >= 9) assp->dumpTree(cout, "-ass: ");
             invarp = outvarp;
-            inp = new AstVarRef(nodep->fileline(), invarp, VAccess::READ);
+            inp = new AstVarRef{nodep->fileline(), invarp, VAccess::READ};
         }
         nodep->replaceWith(inp);
     }
+
+    //========== Move $sampled down to read-only variables
     void visit(AstSampled* nodep) override {
+        if (nodep->user1()) return;
+        VL_RESTORER(m_inSampled);
+        {
+            m_inSampled = true;
+            iterateChildren(nodep);
+        }
         nodep->replaceWith(nodep->exprp()->unlinkFrBack());
         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     }
+    void visit(AstVarRef* nodep) override {
+        iterateChildren(nodep);
+        if (m_inSampled) {
+            if (!nodep->access().isReadOnly()) {
+                nodep->v3warn(E_UNSUPPORTED,
+                              "Unsupported: Write to variable in sampled expression");
+            } else {
+                VNRelinker relinkHandle;
+                nodep->unlinkFrBack(&relinkHandle);
+                AstSampled* const newp = newSampledExpr(nodep);
+                relinkHandle.relink(newp);
+                newp->user1(1);
+            }
+        }
+    }
+    // Don't sample sensitivities
+    void visit(AstSenItem* nodep) override {
+        VL_RESTORER(m_inSampled);
+        {
+            m_inSampled = false;
+            iterateChildren(nodep);
+        }
+    }
 
     //========== Statements
     void visit(AstDisplay* nodep) override {
@@ -387,8 +430,9 @@ private:
                               new AstEq{fl, new AstConst{fl, monNum},
                                         newMonitorNumVarRefp(nodep, VAccess::READ)}},
                 stmtsp};
+            ifp->isBoundsCheck(true);  // To avoid LATCH warning
             ifp->branchPred(VBranchPred::BP_UNLIKELY);
-            AstNode* const newp = new AstAlwaysPostponed{fl, ifp};
+            AstNode* const newp = new AstAlways{fl, VAlwaysKwd::ALWAYS, nullptr, ifp};
             m_modp->addStmtsp(newp);
         } else if (nodep->displayType() == VDisplayType::DT_STROBE) {
             nodep->displayType(VDisplayType::DT_DISPLAY);
@@ -405,6 +449,7 @@ private:
             // Add "always_comb if (__Vstrobe) begin $display(...); __Vstrobe = '0; end"
             AstNode* const stmtsp = nodep;
             AstIf* const ifp = new AstIf{fl, new AstVarRef{fl, varp, VAccess::READ}, stmtsp};
+            ifp->isBoundsCheck(true);  // To avoid LATCH warning
             ifp->branchPred(VBranchPred::BP_UNLIKELY);
             AstNode* const newp = new AstAlwaysPostponed{fl, ifp};
             stmtsp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
@@ -414,8 +459,8 @@ private:
     }
     void visit(AstMonitorOff* nodep) override {
         const auto newp
-            = new AstAssign(nodep->fileline(), newMonitorOffVarRefp(nodep, VAccess::WRITE),
-                            new AstConst(nodep->fileline(), AstConst::BitTrue{}, nodep->off()));
+            = new AstAssign{nodep->fileline(), newMonitorOffVarRefp(nodep, VAccess::WRITE),
+                            new AstConst{nodep->fileline(), AstConst::BitTrue{}, nodep->off()}};
         nodep->replaceWith(newp);
         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     }
diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp
index 8f7b102ef..20652b369 100644
--- a/src/V3Ast.cpp
+++ b/src/V3Ast.cpp
@@ -1075,9 +1075,10 @@ void AstNode::dumpPtrs(std::ostream& os) const {
     if (user5p()) os << " user5p=" << cvtToHex(user5p());
     if (m_iterpp) {
         os << " iterpp=" << cvtToHex(m_iterpp);
-        os << "*=" << cvtToHex(*m_iterpp);
+        // This may cause address sanitizer failures as iterpp can be stale
+        // os << "*=" << cvtToHex(*m_iterpp);
     }
-    os << endl;
+    os << std::endl;
 }
 
 void AstNode::dumpTree(std::ostream& os, const string& indent, int maxDepth) const {
@@ -1141,10 +1142,53 @@ void AstNode::dumpTreeFile(const string& filename, bool append, bool doDump, boo
     }
 }
 
-void AstNode::v3errorEndFatal(std::ostringstream& str) const {
+static void drawChildren(std::ostream& os, const AstNode* thisp, const AstNode* childp,
+                         const std::string& childName) {
+    if (childp) {
+        os << "\tn" << cvtToHex(thisp) << " -> n" << cvtToHex(childp) << " ["
+           << "label=\"" << childName << "\" color=red];\n";
+        for (const AstNode* nodep = childp; nodep; nodep = nodep->nextp()) {
+            nodep->dumpTreeDot(os);
+            if (nodep->nextp()) {
+                os << "\tn" << cvtToHex(nodep) << " -> n" << cvtToHex(nodep->nextp()) << " ["
+                   << "label=\"next\" color=red];\n";
+                os << "\t{rank=same; n" << cvtToHex(nodep) << ", n" << cvtToHex(nodep->nextp())
+                   << "}\n";
+            }
+        }
+    }
+}
+
+void AstNode::dumpTreeDot(std::ostream& os) const {
+    os << "\tn" << cvtToHex(this) << "\t["
+       << "label=\"" << typeName() << "\\n"
+       << name() << "\"];\n";
+    drawChildren(os, this, m_op1p, "op1");
+    drawChildren(os, this, m_op2p, "op2");
+    drawChildren(os, this, m_op3p, "op3");
+    drawChildren(os, this, m_op4p, "op4");
+}
+
+void AstNode::dumpTreeDotFile(const string& filename, bool append, bool doDump) {
+    if (doDump) {
+        UINFO(2, "Dumping " << filename << endl);
+        const std::unique_ptr treedotp{V3File::new_ofstream(filename, append)};
+        if (treedotp->fail()) v3fatal("Can't write " << filename);
+        *treedotp << "digraph vTree{\n";
+        *treedotp << "\tgraph\t[label=\"" << filename + ".dot"
+                  << "\",\n";
+        *treedotp << "\t\t labelloc=t, labeljust=l,\n";
+        *treedotp << "\t\t //size=\"7.5,10\",\n"
+                  << "];\n";
+        dumpTreeDot(*treedotp);
+        *treedotp << "}\n";
+    }
+}
+
+void AstNode::v3errorEndFatal(std::ostringstream& str) const VL_MT_SAFE {
     v3errorEnd(str);
     assert(0);  // LCOV_EXCL_LINE
-    VL_UNREACHABLE
+    VL_UNREACHABLE;
 }
 
 string AstNode::instanceStr() const {
diff --git a/src/V3Ast.h b/src/V3Ast.h
index 01a6af34c..0253e20fe 100644
--- a/src/V3Ast.h
+++ b/src/V3Ast.h
@@ -23,10 +23,12 @@
 #include "V3Broken.h"
 #include "V3Error.h"
 #include "V3FileLine.h"
+#include "V3FunctionTraits.h"
 #include "V3Global.h"
 #include "V3Number.h"
+#include "V3StdFuture.h"
 
-#include "V3Ast__gen_classes.h"  // From ./astgen
+#include "V3Ast__gen_forward_class_decls.h"  // From ./astgen
 
 #include 
 #include 
@@ -87,7 +89,7 @@ using MTaskIdSet = std::set;  // Set of mtaskIds for Var sorting
 
 class VNType final {
 public:
-#include "V3Ast__gen_types.h"  // From ./astgen
+#include "V3Ast__gen_type_enum.h"  // From ./astgen
     // Above include has:
     //   enum en {...};
     //   const char* ascii() const {...};
@@ -95,13 +97,14 @@ public:
     // cppcheck-suppress uninitVar  // responsibility of each subclass
     VNType() = default;
     // cppcheck-suppress noExplicitConstructor
-    constexpr VNType(en _e)
-        : m_e{_e} {}
+    constexpr VNType(en _e) VL_MT_SAFE : m_e{_e} {}
     explicit VNType(int _e)
         : m_e(static_cast(_e)) {}  // Need () or GCC 4.8 false warning
-    constexpr operator en() const { return m_e; }
+    constexpr operator en() const VL_MT_SAFE { return m_e; }
 };
-constexpr bool operator==(const VNType& lhs, const VNType& rhs) { return lhs.m_e == rhs.m_e; }
+constexpr bool operator==(const VNType& lhs, const VNType& rhs) VL_MT_SAFE {
+    return lhs.m_e == rhs.m_e;
+}
 constexpr bool operator==(const VNType& lhs, VNType::en rhs) { return lhs.m_e == rhs; }
 constexpr bool operator==(VNType::en lhs, const VNType& rhs) { return lhs == rhs.m_e; }
 inline std::ostream& operator<<(std::ostream& os, const VNType& rhs) { return os << rhs.ascii(); }
@@ -207,8 +210,8 @@ public:
     explicit VSigning(int _e)
         : m_e(static_cast(_e)) {}  // Need () or GCC 4.8 false warning
     constexpr operator en() const { return m_e; }
-    bool isSigned() const { return m_e == SIGNED; }
-    bool isNosign() const { return m_e == NOSIGN; }
+    bool isSigned() const VL_MT_SAFE { return m_e == SIGNED; }
+    bool isNosign() const VL_MT_SAFE { return m_e == NOSIGN; }
     // No isUnsigned() as it's ambiguous if NOSIGN should be included or not.
 };
 constexpr bool operator==(const VSigning& lhs, const VSigning& rhs) { return lhs.m_e == rhs.m_e; }
@@ -261,66 +264,75 @@ public:
         // in V3Const::visit AstSenTree
         ET_ILLEGAL,
         // Involving a variable
-        ET_ANYEDGE,  // Default for sensitivities; rip them out
-        ET_BOTHEDGE,  // POSEDGE | NEGEDGE
+        ET_CHANGED,  // Value changed
+        ET_BOTHEDGE,  // POSEDGE | NEGEDGE (i.e.: 'edge' in Verilog)
         ET_POSEDGE,
         ET_NEGEDGE,
-        ET_HIGHEDGE,  // Is high now (latches)
-        ET_LOWEDGE,  // Is low now (latches)
-        // Not involving anything
+        ET_EVENT,  // VlEvent::isFired
+        // Involving an expression
+        ET_TRUE,
+        //
         ET_COMBO,  // Sensitive to all combo inputs to this block
-        ET_INITIAL,  // User initial statements
-        ET_SETTLE,  // Like combo but for initial wire resolutions after initial statement
+        ET_HYBRID,  // This is like ET_COMB, but with explicit sensitivity to an expression
+        ET_STATIC,  // static variable initializers (runs before 'initial')
+        ET_INITIAL,  // 'initial' statements
+        ET_FINAL,  // 'final' statements
         ET_NEVER  // Never occurs (optimized away)
     };
     enum en m_e;
     bool clockedStmt() const {
-        static const bool clocked[]
-            = {false, false, true, true, true, true, true, false, false, false};
+        static const bool clocked[] = {
+            false,  // ET_ILLEGAL
+
+            true,  // ET_CHANGED
+            true,  // ET_BOTHEDGE
+            true,  // ET_POSEDGE
+            true,  // ET_NEGEDGE
+            true,  // ET_EVENT
+            true,  // ET_TRUE
+
+            false,  // ET_COMBO
+            false,  // ET_HYBRID
+            false,  // ET_STATIC
+            false,  // ET_INITIAL
+            false,  // ET_FINAL
+            false,  // ET_NEVER
+        };
         return clocked[m_e];
     }
+    bool anEdge() const { return m_e == ET_BOTHEDGE || m_e == ET_POSEDGE || m_e == ET_NEGEDGE; }
     VEdgeType invert() const {
         switch (m_e) {
-        case ET_ANYEDGE: return ET_ANYEDGE;
+        case ET_CHANGED: return ET_CHANGED;
         case ET_BOTHEDGE: return ET_BOTHEDGE;
         case ET_POSEDGE: return ET_NEGEDGE;
         case ET_NEGEDGE: return ET_POSEDGE;
-        case ET_HIGHEDGE: return ET_LOWEDGE;
-        case ET_LOWEDGE: return ET_HIGHEDGE;
         default: UASSERT_STATIC(0, "Inverting bad edgeType()");
         }
         return VEdgeType::ET_ILLEGAL;
     }
     const char* ascii() const {
         static const char* const names[]
-            = {"%E-edge", "ANY",   "BOTH",    "POS",    "NEG",  "HIGH",
-               "LOW",     "COMBO", "INITIAL", "SETTLE", "NEVER"};
+            = {"%E-edge", "CHANGED", "BOTH",   "POS",     "NEG",   "EVENT", "TRUE",
+               "COMBO",   "HYBRID",  "STATIC", "INITIAL", "FINAL", "NEVER"};
         return names[m_e];
     }
     const char* verilogKwd() const {
         static const char* const names[]
-            = {"%E-edge", "[any]", "edge",      "posedge",  "negedge", "[high]",
-               "[low]",   "*",     "[initial]", "[settle]", "[never]"};
+            = {"%E-edge", "[changed]", "edge",     "posedge",   "negedge", "[event]", "[true]",
+               "*",       "[hybrid]",  "[static]", "[initial]", "[final]", "[never]"};
         return names[m_e];
     }
     // Return true iff this and the other have mutually exclusive transitions
     bool exclusiveEdge(const VEdgeType& other) const {
         switch (m_e) {
         case VEdgeType::ET_POSEDGE:
-            switch (other.m_e) {
-            case VEdgeType::ET_NEGEDGE:  // FALLTHRU
-            case VEdgeType::ET_LOWEDGE: return true;
-            default:;
-            }
+            if (other.m_e == VEdgeType::ET_NEGEDGE) return true;
             break;
         case VEdgeType::ET_NEGEDGE:
-            switch (other.m_e) {
-            case VEdgeType::ET_POSEDGE:  // FALLTHRU
-            case VEdgeType::ET_HIGHEDGE: return true;
-            default:;
-            }
+            if (other.m_e == VEdgeType::ET_POSEDGE) return true;
             break;
-        default:;
+        default: break;
         }
         return false;
     }
@@ -373,7 +385,7 @@ public:
         TYPENAME,                       // V3Width processes
         //
         VAR_BASE,                       // V3LinkResolve creates for AstPreSel, V3LinkParam removes
-        VAR_CLOCK_ENABLE,               // V3LinkParse moves to AstVar::attrClockEn
+        VAR_CLOCK_ENABLE,               // Ignored, accepted for compatibility
         VAR_FORCEABLE,                  // V3LinkParse moves to AstVar::isForceable
         VAR_PUBLIC,                     // V3LinkParse moves to AstVar::sigPublic
         VAR_PUBLIC_FLAT,                // V3LinkParse moves to AstVar::sigPublic
@@ -431,7 +443,7 @@ public:
         BIT,
         BYTE,
         CHANDLE,
-        EVENTVALUE,  // See comments in t_event_copy as to why this is EVENTVALUE
+        EVENT,
         INT,
         INTEGER,
         LOGIC,
@@ -445,6 +457,11 @@ public:
         SCOPEPTR,
         CHARPTR,
         MTASKSTATE,
+        TRIGGERVEC,
+        DELAY_SCHEDULER,
+        TRIGGER_SCHEDULER,
+        DYNAMIC_TRIGGER_SCHEDULER,
+        FORK_SYNC,
         // Unsigned and two state; fundamental types
         UINT32,
         UINT64,
@@ -455,19 +472,40 @@ public:
     };
     enum en m_e;
     const char* ascii() const {
-        static const char* const names[]
-            = {"%E-unk",       "bit",     "byte",   "chandle",         "event",
-               "int",          "integer", "logic",  "longint",         "real",
-               "shortint",     "time",    "string", "VerilatedScope*", "char*",
-               "VlMTaskState", "IData",   "QData",  "LOGIC_IMPLICIT",  " MAX"};
+        static const char* const names[] = {"%E-unk",
+                                            "bit",
+                                            "byte",
+                                            "chandle",
+                                            "event",
+                                            "int",
+                                            "integer",
+                                            "logic",
+                                            "longint",
+                                            "real",
+                                            "shortint",
+                                            "time",
+                                            "string",
+                                            "VerilatedScope*",
+                                            "char*",
+                                            "VlMTaskState",
+                                            "VlTriggerVec",
+                                            "VlDelayScheduler",
+                                            "VlTriggerScheduler",
+                                            "VlDynamicTriggerScheduler",
+                                            "VlFork",
+                                            "IData",
+                                            "QData",
+                                            "LOGIC_IMPLICIT",
+                                            " MAX"};
         return names[m_e];
     }
     const char* dpiType() const {
         static const char* const names[]
-            = {"%E-unk",        "svBit",      "char",        "void*",           "char",
-               "int",           "%E-integer", "svLogic",     "long long",       "double",
-               "short",         "%E-time",    "const char*", "dpiScope",        "const char*",
-               "%E-mtaskstate", "IData",      "QData",       "%E-logic-implct", " MAX"};
+            = {"%E-unk",        "svBit",         "char",         "void*",           "char",
+               "int",           "%E-integer",    "svLogic",      "long long",       "double",
+               "short",         "%E-time",       "const char*",  "dpiScope",        "const char*",
+               "%E-mtaskstate", "%E-triggervec", "%E-dly-sched", "%E-trig-sched",   "%E-dyn-sched",
+               "%E-fork",       "IData",         "QData",        "%E-logic-implct", " MAX"};
         return names[m_e];
     }
     static void selfTest() {
@@ -489,7 +527,7 @@ public:
         case BIT: return 1;  // scalar, can't bit extract unless ranged
         case BYTE: return 8;
         case CHANDLE: return 64;
-        case EVENTVALUE: return 1;
+        case EVENT: return 1;
         case INT: return 32;
         case INTEGER: return 32;
         case LOGIC: return 1;  // scalar, can't bit extract unless ranged
@@ -501,6 +539,11 @@ public:
         case SCOPEPTR: return 0;  // opaque
         case CHARPTR: return 0;  // opaque
         case MTASKSTATE: return 0;  // opaque
+        case TRIGGERVEC: return 0;  // opaque
+        case DELAY_SCHEDULER: return 0;  // opaque
+        case TRIGGER_SCHEDULER: return 0;  // opaque
+        case DYNAMIC_TRIGGER_SCHEDULER: return 0;  // opaque
+        case FORK_SYNC: return 0;  // opaque
         case UINT32: return 32;
         case UINT64: return 64;
         default: return 0;
@@ -511,15 +554,14 @@ public:
                || m_e == DOUBLE;
     }
     bool isUnsigned() const {
-        return m_e == CHANDLE || m_e == EVENTVALUE || m_e == STRING || m_e == SCOPEPTR
-               || m_e == CHARPTR || m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC
-               || m_e == TIME;
+        return m_e == CHANDLE || m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
+               || m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC || m_e == TIME;
     }
     bool isFourstate() const {
         return m_e == INTEGER || m_e == LOGIC || m_e == LOGIC_IMPLICIT || m_e == TIME;
     }
     bool isZeroInit() const {  // Otherwise initializes to X
-        return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == EVENTVALUE || m_e == INT
+        return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == EVENT || m_e == INT
                 || m_e == LONGINT || m_e == SHORTINT || m_e == STRING || m_e == DOUBLE);
     }
     bool isIntNumeric() const {  // Enum increment supported
@@ -536,16 +578,18 @@ public:
         return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == INT || m_e == LONGINT
                 || m_e == DOUBLE || m_e == SHORTINT || m_e == UINT32 || m_e == UINT64);
     }
-    bool isOpaque() const {  // IE not a simple number we can bit optimize
-        return (m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR || m_e == MTASKSTATE
+    bool isOpaque() const VL_MT_SAFE {  // IE not a simple number we can bit optimize
+        return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
+                || m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER
+                || m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC
                 || m_e == DOUBLE);
     }
-    bool isDouble() const { return m_e == DOUBLE; }
-    bool isEventValue() const { return m_e == EVENTVALUE; }
-    bool isString() const { return m_e == STRING; }
-    bool isMTaskState() const { return m_e == MTASKSTATE; }
+    bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
+    bool isEvent() const { return m_e == EVENT; }
+    bool isString() const VL_MT_SAFE { return m_e == STRING; }
+    bool isMTaskState() const VL_MT_SAFE { return m_e == MTASKSTATE; }
     // Does this represent a C++ LiteralType? (can be constexpr)
-    bool isLiteralType() const {
+    bool isLiteralType() const VL_MT_SAFE {
         switch (m_e) {
         case BIT:
         case BYTE:
@@ -564,13 +608,13 @@ public:
         }
     }
 };
-constexpr bool operator==(const VBasicDTypeKwd& lhs, const VBasicDTypeKwd& rhs) {
+constexpr bool operator==(const VBasicDTypeKwd& lhs, const VBasicDTypeKwd& rhs) VL_MT_SAFE {
     return lhs.m_e == rhs.m_e;
 }
-constexpr bool operator==(const VBasicDTypeKwd& lhs, VBasicDTypeKwd::en rhs) {
+constexpr bool operator==(const VBasicDTypeKwd& lhs, VBasicDTypeKwd::en rhs) VL_MT_SAFE {
     return lhs.m_e == rhs;
 }
-constexpr bool operator==(VBasicDTypeKwd::en lhs, const VBasicDTypeKwd& rhs) {
+constexpr bool operator==(VBasicDTypeKwd::en lhs, const VBasicDTypeKwd& rhs) VL_MT_SAFE {
     return lhs == rhs.m_e;
 }
 
@@ -587,7 +631,7 @@ public:
         : m_e{_e} {}
     explicit VDirection(int _e)
         : m_e(static_cast(_e)) {}  // Need () or GCC 4.8 false warning
-    constexpr operator en() const { return m_e; }
+    constexpr operator en() const VL_MT_SAFE { return m_e; }
     const char* ascii() const {
         static const char* const names[] = {"NONE", "INPUT", "OUTPUT", "INOUT", "REF", "CONSTREF"};
         return names[m_e];
@@ -607,9 +651,9 @@ public:
     bool isNonOutput() const {
         return m_e == INPUT || m_e == INOUT || m_e == REF || m_e == CONSTREF;
     }
-    bool isReadOnly() const { return m_e == INPUT || m_e == CONSTREF; }
-    bool isWritable() const { return m_e == OUTPUT || m_e == INOUT || m_e == REF; }
-    bool isRefOrConstRef() const { return m_e == REF || m_e == CONSTREF; }
+    bool isReadOnly() const VL_MT_SAFE { return m_e == INPUT || m_e == CONSTREF; }
+    bool isWritable() const VL_MT_SAFE { return m_e == OUTPUT || m_e == INOUT || m_e == REF; }
+    bool isRefOrConstRef() const VL_MT_SAFE { return m_e == REF || m_e == CONSTREF; }
 };
 constexpr bool operator==(const VDirection& lhs, const VDirection& rhs) {
     return lhs.m_e == rhs.m_e;
@@ -722,11 +766,9 @@ public:
         MEMBER
     };
     enum en m_e;
-    VVarType()
-        : m_e{UNKNOWN} {}
+    VVarType() VL_MT_SAFE : m_e{UNKNOWN} {}
     // cppcheck-suppress noExplicitConstructor
-    constexpr VVarType(en _e)
-        : m_e{_e} {}
+    constexpr VVarType(en _e) VL_MT_SAFE : m_e{_e} {}
     explicit VVarType(int _e)
         : m_e(static_cast(_e)) {}  // Need () or GCC 4.8 false warning
     constexpr operator en() const { return m_e; }
@@ -760,10 +802,16 @@ public:
         return (m_e == BLOCKTEMP || m_e == MODULETEMP || m_e == STMTTEMP || m_e == XTEMP);
     }
 };
-constexpr bool operator==(const VVarType& lhs, const VVarType& rhs) { return lhs.m_e == rhs.m_e; }
-constexpr bool operator==(const VVarType& lhs, VVarType::en rhs) { return lhs.m_e == rhs; }
-constexpr bool operator==(VVarType::en lhs, const VVarType& rhs) { return lhs == rhs.m_e; }
-inline std::ostream& operator<<(std::ostream& os, const VVarType& rhs) {
+constexpr bool operator==(const VVarType& lhs, const VVarType& rhs) VL_MT_SAFE {
+    return lhs.m_e == rhs.m_e;
+}
+constexpr bool operator==(const VVarType& lhs, VVarType::en rhs) VL_MT_SAFE {
+    return lhs.m_e == rhs;
+}
+constexpr bool operator==(VVarType::en lhs, const VVarType& rhs) VL_MT_SAFE {
+    return lhs == rhs.m_e;
+}
+inline std::ostream& operator<<(std::ostream& os, const VVarType& rhs) VL_MT_SAFE {
     return os << rhs.ascii();
 }
 
@@ -1062,10 +1110,14 @@ public:
     }
     int left() const { return m_left; }
     int right() const { return m_right; }
-    int hi() const { return m_left > m_right ? m_left : m_right; }  // How to show a declaration
-    int lo() const { return m_left > m_right ? m_right : m_left; }  // How to show a declaration
+    int hi() const VL_MT_SAFE {
+        return m_left > m_right ? m_left : m_right;
+    }  // How to show a declaration
+    int lo() const VL_MT_SAFE {
+        return m_left > m_right ? m_right : m_left;
+    }  // How to show a declaration
     int leftToRightInc() const { return littleEndian() ? 1 : -1; }
-    int elements() const { return hi() - lo() + 1; }
+    int elements() const VL_MT_SAFE { return hi() - lo() + 1; }
     bool ranged() const { return m_ranged; }
     bool littleEndian() const { return m_left < m_right; }
     int hiMaxSelect() const {
@@ -1179,12 +1231,12 @@ public:
     ~VNUser() = default;
     // Casters
     template 
-    typename std::enable_if::value, T>::type to() const {
+    typename std::enable_if::value, T>::type to() const VL_MT_SAFE {
         return reinterpret_cast(m_u.up);
     }
     WidthVP* c() const { return to(); }
     VSymEnt* toSymEnt() const { return to(); }
-    AstNode* toNodep() const { return to(); }
+    AstNode* toNodep() const VL_MT_SAFE { return to(); }
     V3GraphVertex* toGraphVertex() const { return to(); }
     int toInt() const { return m_u.ui; }
     static VNUser fromInt(int i) { return VNUser{i}; }
@@ -1487,7 +1539,7 @@ class AstNode VL_NOT_FINAL {
 private:
     AstNode* cloneTreeIter();
     AstNode* cloneTreeIterList();
-    void checkTreeIter(const AstNode* backp) const;
+    void checkTreeIter(const AstNode* backp) const VL_MT_SAFE;
     bool gateTreeIter() const;
     static bool sameTreeIter(const AstNode* node1p, const AstNode* node2p, bool ignNext,
                              bool gateOnly);
@@ -1541,16 +1593,16 @@ protected:
 
 public:
     // ACCESSORS
-    VNType type() const { return m_type; }
-    const char* typeName() const { return type().ascii(); }  // See also prettyTypeName
-    AstNode* nextp() const { return m_nextp; }
-    AstNode* backp() const { return m_backp; }
+    VNType type() const VL_MT_SAFE { return m_type; }
+    const char* typeName() const VL_MT_SAFE { return type().ascii(); }  // See also prettyTypeName
+    AstNode* nextp() const VL_MT_SAFE { return m_nextp; }
+    AstNode* backp() const VL_MT_SAFE { return m_backp; }
     AstNode* abovep() const;  // Parent node above, only when no nextp() as otherwise slow
-    AstNode* op1p() const { return m_op1p; }
-    AstNode* op2p() const { return m_op2p; }
-    AstNode* op3p() const { return m_op3p; }
-    AstNode* op4p() const { return m_op4p; }
-    AstNodeDType* dtypep() const { return m_dtypep; }
+    AstNode* op1p() const VL_MT_SAFE { return m_op1p; }
+    AstNode* op2p() const VL_MT_SAFE { return m_op2p; }
+    AstNode* op3p() const VL_MT_SAFE { return m_op3p; }
+    AstNode* op4p() const VL_MT_SAFE { return m_op4p; }
+    AstNodeDType* dtypep() const VL_MT_SAFE { return m_dtypep; }
     AstNode* clonep() const { return ((m_cloneCnt == s_cloneCntGbl) ? m_clonep : nullptr); }
     AstNode* firstAbovep() const {  // Returns nullptr when second or later in list
         return ((backp() && backp()->nextp() != this) ? backp() : nullptr);
@@ -1565,7 +1617,7 @@ public:
                 // If we're first in the list, check what backp() thinks of us:
                 || (backp() && backp()->isFirstInMyListOfStatements(this)));
     }
-    uint8_t brokenState() const { return m_brokenState; }
+    uint8_t brokenState() const VL_MT_SAFE { return m_brokenState; }
     void brokenState(uint8_t value) { m_brokenState = value; }
 
     // Used by AstNode::broken()
@@ -1597,7 +1649,7 @@ public:
     static constexpr int INSTR_COUNT_PLI = 20;  // PLI routines
 
     // ACCESSORS
-    virtual string name() const { return ""; }
+    virtual string name() const VL_MT_SAFE { return ""; }
     virtual string origName() const { return ""; }
     virtual void name(const string& name) {
         this->v3fatalSrc("name() called on object without name() method");
@@ -1605,7 +1657,7 @@ public:
     virtual void tag(const string& text) {}
     virtual string tag() const { return ""; }
     virtual string verilogKwd() const { return ""; }
-    string nameProtect() const;  // Name with --protect-id applied
+    string nameProtect() const VL_MT_SAFE;  // Name with --protect-id applied
     string origNameProtect() const;  // origName with --protect-id applied
     string shortName() const;  // Name with __PVT__ removed for concatenating scopes
     static string dedotName(const string& namein);  // Name with dots removed
@@ -1617,11 +1669,11 @@ public:
     encodeName(const string& namein);  // Encode user name into internal C representation
     static string encodeNumber(int64_t num);  // Encode number into internal C representation
     static string vcdName(const string& namein);  // Name for printing out to vcd files
-    string prettyName() const { return prettyName(name()); }
+    string prettyName() const VL_MT_SAFE { return prettyName(name()); }
     string prettyNameQ() const { return prettyNameQ(name()); }
     string prettyTypeName() const;  // "VARREF" for error messages (NOT dtype's pretty name)
     virtual string prettyOperatorName() const { return "operator " + prettyTypeName(); }
-    FileLine* fileline() const { return m_fileline; }
+    FileLine* fileline() const VL_MT_SAFE { return m_fileline; }
     void fileline(FileLine* fl) { m_fileline = fl; }
     inline bool width1() const;
     inline int widthInstrs() const;
@@ -1634,29 +1686,29 @@ public:
     }
     bool doingWidth() const { return m_flags.doingWidth; }
     void doingWidth(bool flag) { m_flags.doingWidth = flag; }
-    bool protect() const { return m_flags.protect; }
+    bool protect() const VL_MT_SAFE { return m_flags.protect; }
     void protect(bool flag) { m_flags.protect = flag; }
 
     // TODO stomp these width functions out, and call via dtypep() instead
-    inline int width() const;
+    inline int width() const VL_MT_SAFE;
     inline int widthMin() const;
     int widthMinV() const {
         return v3Global.widthMinUsage() == VWidthMinUsage::VERILOG_WIDTH ? widthMin() : width();
     }
     int widthWords() const { return VL_WORDS_I(width()); }
-    bool isQuad() const { return (width() > VL_IDATASIZE && width() <= VL_QUADSIZE); }
-    bool isWide() const { return (width() > VL_QUADSIZE); }
+    bool isQuad() const VL_MT_SAFE { return (width() > VL_IDATASIZE && width() <= VL_QUADSIZE); }
+    bool isWide() const VL_MT_SAFE { return (width() > VL_QUADSIZE); }
     inline bool isDouble() const;
     inline bool isSigned() const;
     inline bool isString() const;
 
     // clang-format off
-    VNUser      user1u() const {
+    VNUser      user1u() const VL_MT_SAFE {
         // Slows things down measurably, so disabled by default
         //UASSERT_STATIC(VNUser1InUse::s_userBusy, "userp set w/o busy");
         return ((m_user1Cnt==VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{0});
     }
-    AstNode*    user1p() const { return user1u().toNodep(); }
+    AstNode*    user1p() const VL_MT_SAFE { return user1u().toNodep(); }
     void        user1u(const VNUser& user) { m_user1u=user; m_user1Cnt=VNUser1InUse::s_userCntGbl; }
     void        user1p(void* userp) { user1u(VNUser{userp}); }
     int         user1() const { return user1u().toInt(); }
@@ -1665,12 +1717,12 @@ public:
     int         user1SetOnce() { int v=user1(); if (!v) user1(1); return v; }  // Better for cache than user1Inc()
     static void user1ClearTree() { VNUser1InUse::clear(); }  // Clear userp()'s across the entire tree
 
-    VNUser      user2u() const {
+    VNUser      user2u() const VL_MT_SAFE {
         // Slows things down measurably, so disabled by default
         //UASSERT_STATIC(VNUser2InUse::s_userBusy, "userp set w/o busy");
         return ((m_user2Cnt==VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{0});
     }
-    AstNode*    user2p() const { return user2u().toNodep(); }
+    AstNode*    user2p() const VL_MT_SAFE { return user2u().toNodep(); }
     void        user2u(const VNUser& user) { m_user2u=user; m_user2Cnt=VNUser2InUse::s_userCntGbl; }
     void        user2p(void* userp) { user2u(VNUser{userp}); }
     int         user2() const { return user2u().toInt(); }
@@ -1679,12 +1731,12 @@ public:
     int         user2SetOnce() { int v=user2(); if (!v) user2(1); return v; }  // Better for cache than user2Inc()
     static void user2ClearTree() { VNUser2InUse::clear(); }  // Clear userp()'s across the entire tree
 
-    VNUser      user3u() const {
+    VNUser      user3u() const VL_MT_SAFE {
         // Slows things down measurably, so disabled by default
         //UASSERT_STATIC(VNUser3InUse::s_userBusy, "userp set w/o busy");
         return ((m_user3Cnt==VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{0});
     }
-    AstNode*    user3p() const { return user3u().toNodep(); }
+    AstNode*    user3p() const VL_MT_SAFE { return user3u().toNodep(); }
     void        user3u(const VNUser& user) { m_user3u=user; m_user3Cnt=VNUser3InUse::s_userCntGbl; }
     void        user3p(void* userp) { user3u(VNUser{userp}); }
     int         user3() const { return user3u().toInt(); }
@@ -1693,12 +1745,12 @@ public:
     int         user3SetOnce() { int v=user3(); if (!v) user3(1); return v; }  // Better for cache than user3Inc()
     static void user3ClearTree() { VNUser3InUse::clear(); }  // Clear userp()'s across the entire tree
 
-    VNUser      user4u() const {
+    VNUser      user4u() const VL_MT_SAFE {
         // Slows things down measurably, so disabled by default
         //UASSERT_STATIC(VNUser4InUse::s_userBusy, "userp set w/o busy");
         return ((m_user4Cnt==VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{0});
     }
-    AstNode*    user4p() const { return user4u().toNodep(); }
+    AstNode*    user4p() const VL_MT_SAFE { return user4u().toNodep(); }
     void        user4u(const VNUser& user) { m_user4u=user; m_user4Cnt=VNUser4InUse::s_userCntGbl; }
     void        user4p(void* userp) { user4u(VNUser{userp}); }
     int         user4() const { return user4u().toInt(); }
@@ -1707,12 +1759,12 @@ public:
     int         user4SetOnce() { int v=user4(); if (!v) user4(1); return v; }  // Better for cache than user4Inc()
     static void user4ClearTree() { VNUser4InUse::clear(); }  // Clear userp()'s across the entire tree
 
-    VNUser      user5u() const {
+    VNUser      user5u() const VL_MT_SAFE {
         // Slows things down measurably, so disabled by default
         //UASSERT_STATIC(VNUser5InUse::s_userBusy, "userp set w/o busy");
         return ((m_user5Cnt==VNUser5InUse::s_userCntGbl) ? m_user5u : VNUser{0});
     }
-    AstNode*    user5p() const { return user5u().toNodep(); }
+    AstNode*    user5p() const VL_MT_SAFE { return user5u().toNodep(); }
     void        user5u(const VNUser& user) { m_user5u=user; m_user5Cnt=VNUser5InUse::s_userCntGbl; }
     void        user5p(void* userp) { user5u(VNUser{userp}); }
     int         user5() const { return user5u().toInt(); }
@@ -1730,8 +1782,8 @@ public:
 #else
     void editCountInc() { ++s_editCntGbl; }
 #endif
-    static uint64_t editCountLast() { return s_editCntLast; }
-    static uint64_t editCountGbl() { return s_editCntGbl; }
+    static uint64_t editCountLast() VL_MT_SAFE { return s_editCntLast; }
+    static uint64_t editCountGbl() VL_MT_SAFE { return s_editCntGbl; }
     static void editCountSetLast() { s_editCntLast = editCountGbl(); }
 
     // ACCESSORS for specific types
@@ -1796,8 +1848,8 @@ public:
     static AstBasicDType* findInsertSameDType(AstBasicDType* nodep);
 
     // METHODS - dump and error
-    void v3errorEnd(std::ostringstream& str) const;
-    void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN;
+    void v3errorEnd(std::ostringstream& str) const VL_MT_SAFE;
+    void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN VL_MT_SAFE;
     string warnContextPrimary() const { return fileline()->warnContextPrimary(); }
     string warnContextSecondary() const { return fileline()->warnContextSecondary(); }
     string warnMore() const { return fileline()->warnMore(); }
@@ -1846,7 +1898,7 @@ public:
     // Does tree of this == node2p?, not allowing non-isGateOptimizable
     inline bool sameGateTree(const AstNode* node2p) const;
     void deleteTree();  // Always deletes the next link
-    void checkTree() const {
+    void checkTree() const VL_MT_SAFE {
         if (v3Global.opt.debugCheck()) checkTreeIter(backp());
     }
     void checkIter() const;
@@ -1862,35 +1914,39 @@ public:
     void dumpTreeFile(const string& filename, bool append = false, bool doDump = true,
                       bool doCheck = true);
     static void dumpTreeFileGdb(const AstNode* nodep, const char* filenamep = nullptr);
+    void dumpTreeDot(std::ostream& os = std::cout) const;
+    void dumpTreeDotFile(const string& filename, bool append = false, bool doDump = true);
 
     // METHODS - queries
     // Changes control flow, disable some optimizations
     virtual bool isBrancher() const { return false; }
     // Else a AstTime etc that can't be pushed out
-    virtual bool isGateOptimizable() const { return true; }
+    virtual bool isGateOptimizable() const { return !isTimingControl(); }
     // GateDedupable is a slightly larger superset of GateOptimzable (eg, AstNodeIf)
     virtual bool isGateDedupable() const { return isGateOptimizable(); }
     // Else creates output or exits, etc, not unconsumed
     virtual bool isOutputter() const { return false; }
     // Else a AstTime etc which output can't be predicted from input
-    virtual bool isPredictOptimizable() const { return true; }
+    virtual bool isPredictOptimizable() const { return !isTimingControl(); }
     // Else a $display, etc, that must be ordered with other displays
     virtual bool isPure() const { return true; }
     // Else a AstTime etc that can't be substituted out
     virtual bool isSubstOptimizable() const { return true; }
+    // An event control, delay, wait, etc.
+    virtual bool isTimingControl() const { return false; }
     // isUnlikely handles $stop or similar statement which means an above IF
     // statement is unlikely to be taken
     virtual bool isUnlikely() const { return false; }
     virtual int instrCount() const { return 0; }
     virtual bool same(const AstNode*) const { return true; }
     // Iff has a data type; dtype() must be non null
-    virtual bool hasDType() const { return false; }
+    virtual bool hasDType() const VL_MT_SAFE { return false; }
     // Iff has a non-null childDTypep(), as generic node function
     virtual AstNodeDType* getChildDTypep() const { return nullptr; }
     // Iff has a non-null child2DTypep(), as generic node function
     virtual AstNodeDType* getChild2DTypep() const { return nullptr; }
     // Another AstNode* may have a pointer into this node, other then normal front/back/etc.
-    virtual bool maybePointedTo() const { return false; }
+    virtual bool maybePointedTo() const VL_MT_SAFE { return false; }
     // Don't reclaim this node in V3Dead
     virtual bool undead() const { return false; }
     // Check if node is consistent, return nullptr if ok, else reason string
@@ -1944,7 +2000,7 @@ private:
 public:
     // For use via the VN_IS macro only
     template 
-    static bool privateIs(const AstNode* nodep) {
+    static bool privateIs(const AstNode* nodep) VL_MT_SAFE {
         static_assert(!uselessCast(), "Unnecessary VN_IS, node known to have target type.");
         static_assert(!impossibleCast(), "Unnecessary VN_IS, node cannot be this type.");
         return nodep && privateTypeTest(nodep);
@@ -1952,14 +2008,14 @@ public:
 
     // For use via the VN_CAST macro only
     template 
-    static T* privateCast(AstNode* nodep) {
+    static T* privateCast(AstNode* nodep) VL_MT_SAFE {
         static_assert(!uselessCast(),
                       "Unnecessary VN_CAST, node known to have target type.");
         static_assert(!impossibleCast(), "Unnecessary VN_CAST, node cannot be this type.");
         return nodep && privateTypeTest(nodep) ? reinterpret_cast(nodep) : nullptr;
     }
     template 
-    static const T* privateCast(const AstNode* nodep) {
+    static const T* privateCast(const AstNode* nodep) VL_MT_SAFE {
         static_assert(!uselessCast(),
                       "Unnecessary VN_CAST, node known to have target type.");
         static_assert(!impossibleCast(), "Unnecessary VN_CAST, node cannot be this type.");
@@ -1968,7 +2024,7 @@ public:
 
     // For use via the VN_AS macro only
     template 
-    static T* privateAs(AstNode* nodep) {
+    static T* privateAs(AstNode* nodep) VL_MT_SAFE {
         static_assert(!uselessCast(), "Unnecessary VN_AS, node known to have target type.");
         static_assert(!impossibleCast(), "Unnecessary VN_AS, node cannot be this type.");
         UASSERT_OBJ(!nodep || privateTypeTest(nodep), nodep,
@@ -1977,7 +2033,7 @@ public:
         return reinterpret_cast(nodep);
     }
     template 
-    static const T* privateAs(const AstNode* nodep) {
+    static const T* privateAs(const AstNode* nodep) VL_MT_SAFE {
         static_assert(!uselessCast(), "Unnecessary VN_AS, node known to have target type.");
         static_assert(!impossibleCast(), "Unnecessary VN_AS, node cannot be this type.");
         UASSERT_OBJ(!nodep || privateTypeTest(nodep), nodep,
@@ -2016,95 +2072,127 @@ private:
     using ConstCorrectAstNode =
         typename std::conditional::value, const AstNode, AstNode>::type;
 
-    template 
-    inline static void foreachImpl(ConstCorrectAstNode* nodep,
-                                   const std::function& f, bool visitNext);
+    template 
+    inline static void foreachImpl(ConstCorrectAstNode* nodep, const Callable& f,
+                                   bool visitNext);
 
-    template 
-    inline static bool predicateImpl(ConstCorrectAstNode* nodep,
-                                     const std::function& p);
+    template 
+    inline static bool predicateImpl(ConstCorrectAstNode* nodep, const Callable& p);
 
-    template 
-    constexpr static bool checkTypeParameter() {
-        static_assert(!std::is_const::value,
-                      "Type parameter 'T_Node' should not be const qualified");
-        static_assert(std::is_base_of::value,
-                      "Type parameter 'T_Node' must be a subtype of AstNode");
-        return true;
-    }
+    template 
+    struct Arg0NoPointerNoCV final {
+        using Traits = FunctionTraits;
+        using T_Arg0 = typename Traits::template arg<0>::type;
+        using T_Arg0NoPtr = typename std::remove_pointer::type;
+        using type = typename std::remove_cv::type;
+    };
 
 public:
-    // Traverse subtree and call given function 'f' in pre-order on each node that has type
-    // 'T_Node'. The node passd to the function 'f' can be removed or replaced, but other editing
+    // Given a callable 'f' that takes a single argument of some AstNode subtype 'T_Node', traverse
+    // the tree rooted at this node, and call 'f' in pre-order on each node that is of type
+    // 'T_Node'. The node passed to the callable 'f' can be removed or replaced, but other editing
     // of the iterated tree is not safe. Prefer 'foreach' over simple VNVisitor that only needs to
     // handle a single (or a few) node types, as it's easier to write, but more importantly, the
-    // dispatch to the operation function in 'foreach' should be completely predictable by branch
-    // target caches in modern CPUs, while it is basically unpredictable for VNVisitor.
-    template 
-    void foreach (std::function f) {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    // dispatch to the callable in 'foreach' should be completely predictable by branch target
+    // caches in modern CPUs, while it is basically unpredictable for VNVisitor.
+    template 
+    void foreach(Callable&& f) {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable::value
+                          && std::is_base_of::value,
+                      "Callable 'f' must have a signature compatible with 'void(T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         foreachImpl(this, f, /* visitNext: */ false);
     }
 
     // Same as above, but for 'const' nodes
-    template 
-    void foreach (std::function f) const {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    template 
+    void foreach(Callable&& f) const {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable::value
+                          && std::is_base_of::value,
+                      "Callable 'f' must have a signature compatible with 'void(const T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         foreachImpl(this, f, /* visitNext: */ false);
     }
 
-    // Same as 'foreach' but also follows 'this->nextp()'
-    template 
-    void foreachAndNext(std::function f) {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    // Same as 'foreach' but also traverses 'this->nextp()' transitively
+    template 
+    void foreachAndNext(Callable&& f) {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable::value
+                          && std::is_base_of::value,
+                      "Callable 'f' must have a signature compatible with 'void(T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         foreachImpl(this, f, /* visitNext: */ true);
     }
 
-    // Same as 'foreach' but also follows 'this->nextp()'
-    template 
-    void foreachAndNext(std::function f) const {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    // Same as above, but for 'const' nodes
+    template 
+    void foreachAndNext(Callable&& f) const {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable::value
+                          && std::is_base_of::value,
+                      "Callable 'f' must have a signature compatible with 'void(const T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         foreachImpl(this, f, /* visitNext: */ true);
     }
 
-    // Given a predicate function 'p' return true if and only if there exists a node of type
-    // 'T_Node' that satisfies the predicate 'p'. Returns false if no node of type 'T_Node' is
-    // present. Traversal is performed in some arbitrary order and is terminated as soon as the
-    // result can be determined.
-    template 
-    bool exists(std::function p) {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    // Given a predicate 'p' that takes a single argument of some AstNode subtype 'T_Node', return
+    // true if and only if there exists a node of type 'T_Node' in the tree rooted at this node,
+    // that satisfies the predicate 'p'. Returns false if no node of type 'T_Node' is present.
+    // Traversal is performed in some arbitrary order and is terminated as soon as the result can
+    // be determined.
+    template 
+    bool exists(Callable&& p) {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable_r::value
+                          && std::is_base_of::value,
+                      "Predicate 'p' must have a signature compatible with 'bool(T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         return predicateImpl(this, p);
     }
 
     // Same as above, but for 'const' nodes
-    template 
-    bool exists(std::function p) const {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    template 
+    bool exists(Callable&& p) const {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable_r::value
+                          && std::is_base_of::value,
+                      "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         return predicateImpl(this, p);
     }
 
-    // Given a predicate function 'p' return true if and only if all nodes of type
-    // 'T_Node' satisfy the predicate 'p'. Returns true if no node of type 'T_Node' is
-    // present. Traversal is performed in some arbitrary order and is terminated as soon as the
-    // result can be determined.
-    template 
-    bool forall(std::function p) {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    // Given a predicate 'p' that takes a single argument of some AstNode subtype 'T_Node', return
+    // true if and only if all nodes of type 'T_Node' in the tree rooted at this node satisfy the
+    // predicate 'p'. Returns true if no node of type 'T_Node' is present. Traversal is performed
+    // in some arbitrary order and is terminated as soon as the result can be determined.
+    template 
+    bool forall(Callable&& p) {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable_r::value
+                          && std::is_base_of::value,
+                      "Predicate 'p' must have a signature compatible with 'bool(T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         return predicateImpl(this, p);
     }
 
     // Same as above, but for 'const' nodes
-    template 
-    bool forall(std::function p) const {
-        static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'");
+    template 
+    bool forall(Callable&& p) const {
+        using T_Node = typename Arg0NoPointerNoCV::type;
+        static_assert(vlstd::is_invocable_r::value
+                          && std::is_base_of::value,
+                      "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', "
+                      "with 'T_Node' being a subtype of 'AstNode'");
         return predicateImpl(this, p);
     }
 
     int nodeCount() const {
         // TODO: this should really return size_t, but need to fix use sites
         int count = 0;
-        this->foreach([&count](const AstNode*) { ++count; });
+        this->foreach([&count](const AstNode*) { ++count; });
         return count;
     }
 };
@@ -2121,7 +2209,7 @@ void AstNode::addPrev(AstNode* newp) {
 }
 
 // Specialisations of privateTypeTest
-#include "V3Ast__gen_impl.h"  // From ./astgen
+#include "V3Ast__gen_type_tests.h"  // From ./astgen
 
 // Specializations of AstNode::mayBeUnder
 template <>
@@ -2147,6 +2235,18 @@ inline bool AstNode::mayBeUnder(const AstNode* nodep) {
     if (VN_IS(nodep, NodeStmt)) return false;  // Should be directly under CFunc
     return true;
 }
+template <>
+inline bool AstNode::mayBeUnder(const AstNode* nodep) {
+    return !VN_IS(nodep, Active);  // AstActives do not nest
+}
+template <>
+inline bool AstNode::mayBeUnder(const AstNode* nodep) {
+    return !VN_IS(nodep, Scope);  // AstScopes do not nest
+}
+template <>
+inline bool AstNode::mayBeUnder(const AstNode* nodep) {
+    return !VN_IS(nodep, SenTree);  // AstSenTree do not nest
+}
 
 // Specializations of AstNode::isLeaf
 template <>
@@ -2163,172 +2263,161 @@ constexpr bool AstNode::isLeaf() {
 }
 
 // foreach implementation
-template 
-void AstNode::foreachImpl(ConstCorrectAstNode* nodep, const std::function& f,
-                          bool visitNext) {
-    // Checking the function is bound up front eliminates this check from the loop at invocation
-    if (!f) {
-        nodep->v3fatal("AstNode::foreach called with unbound function");  // LCOV_EXCL_LINE
-    } else {
-        // Pre-order traversal implemented directly (without recursion) for speed reasons. The very
-        // first iteration (the one that operates on the input nodep) is special, as we might or
-        // might not need to enqueue nodep->nextp() depending on VisitNext, while in all other
-        // iterations, we do want to enqueue nodep->nextp(). Duplicating code (via
-        // 'foreachImplVisit') for the initial iteration here to avoid an extra branch in the loop
+template 
+void AstNode::foreachImpl(ConstCorrectAstNode* nodep, const Callable& f, bool visitNext) {
+    // Pre-order traversal implemented directly (without recursion) for speed reasons. The very
+    // first iteration (the one that operates on the input nodep) is special, as we might or
+    // might not need to enqueue nodep->nextp() depending on VisitNext, while in all other
+    // iterations, we do want to enqueue nodep->nextp(). Duplicating code (via
+    // 'foreachImplVisit') for the initial iteration here to avoid an extra branch in the loop
 
-        using T_Arg_NonConst = typename std::remove_const::type;
-        using Node = ConstCorrectAstNode;
+    using T_Arg_NonConst = typename std::remove_const::type;
+    using Node = ConstCorrectAstNode;
 
-        // Traversal stack
-        std::vector stack;  // Kept as a vector for easy resizing
-        Node** basep = nullptr;  // Pointer to base of stack
-        Node** topp = nullptr;  // Pointer to top of stack
-        Node** limp = nullptr;  // Pointer to stack limit (when need growing)
+    // Traversal stack
+    std::vector stack;  // Kept as a vector for easy resizing
+    Node** basep = nullptr;  // Pointer to base of stack
+    Node** topp = nullptr;  // Pointer to top of stack
+    Node** limp = nullptr;  // Pointer to stack limit (when need growing)
 
-        // We prefetch this far into the stack
-        constexpr int prefetchDistance = 2;
+    // We prefetch this far into the stack
+    constexpr int prefetchDistance = 2;
 
-        // Grow stack to given size
-        const auto grow = [&](size_t size) {
-            const ptrdiff_t occupancy = topp - basep;
-            stack.resize(size);
-            basep = stack.data() + prefetchDistance;
-            topp = basep + occupancy;
-            limp = basep + size - 5;  // We push max 5 items per iteration
-        };
+    // Grow stack to given size
+    const auto grow = [&](size_t size) {
+        const ptrdiff_t occupancy = topp - basep;
+        stack.resize(size);
+        basep = stack.data() + prefetchDistance;
+        topp = basep + occupancy;
+        limp = basep + size - 5;  // We push max 5 items per iteration
+    };
 
-        // Initial stack size
-        grow(32);
+    // Initial stack size
+    grow(32);
 
-        // We want some non-null pointers at the beginning. These will be prefetched, but not
-        // visited, so the root node will suffice. This eliminates needing branches in the loop.
-        for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
+    // We want some non-null pointers at the beginning. These will be prefetched, but not
+    // visited, so the root node will suffice. This eliminates needing branches in the loop.
+    for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
 
-        // Visit given node, enqueue children for traversal
-        const auto visit = [&](Node* currp) {
-            // Type test this node
-            if (AstNode::privateTypeTest(currp)) {
-                // Call the client function
-                f(static_cast(currp));
-                // Short circuit if iterating leaf nodes
-                if VL_CONSTEXPR_CXX17 (isLeaf()) return;
-            }
-
-            // Enqueue children for traversal, unless futile
-            if (mayBeUnder(currp)) {
-                if (AstNode* const op4p = currp->op4p()) *topp++ = op4p;
-                if (AstNode* const op3p = currp->op3p()) *topp++ = op3p;
-                if (AstNode* const op2p = currp->op2p()) *topp++ = op2p;
-                if (AstNode* const op1p = currp->op1p()) *topp++ = op1p;
-            }
-        };
-
-        // Enqueue the next of the root node, if required
-        if (visitNext && nodep->nextp()) *topp++ = nodep->nextp();
-
-        // Visit the root node
-        visit(nodep);
-
-        // Visit the rest of the tree
-        while (VL_LIKELY(topp > basep)) {
-            // Pop next node in the traversal
-            Node* const headp = *--topp;
-
-            // Prefetch in case we are ascending the tree
-            ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
-
-            // Ensure we have stack space for nextp and the 4 children
-            if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
-
-            // Enqueue the next node
-            if (headp->nextp()) *topp++ = headp->nextp();
-
-            // Visit the head node
-            visit(headp);
+    // Visit given node, enqueue children for traversal
+    const auto visit = [&](Node* currp) {
+        // Type test this node
+        if (AstNode::privateTypeTest(currp)) {
+            // Call the client function
+            f(static_cast(currp));
+            // Short circuit if iterating leaf nodes
+            if VL_CONSTEXPR_CXX17 (isLeaf()) return;
         }
+
+        // Enqueue children for traversal, unless futile
+        if (mayBeUnder(currp)) {
+            if (AstNode* const op4p = currp->op4p()) *topp++ = op4p;
+            if (AstNode* const op3p = currp->op3p()) *topp++ = op3p;
+            if (AstNode* const op2p = currp->op2p()) *topp++ = op2p;
+            if (AstNode* const op1p = currp->op1p()) *topp++ = op1p;
+        }
+    };
+
+    // Enqueue the next of the root node, if required
+    if (visitNext && nodep->nextp()) *topp++ = nodep->nextp();
+
+    // Visit the root node
+    visit(nodep);
+
+    // Visit the rest of the tree
+    while (VL_LIKELY(topp > basep)) {
+        // Pop next node in the traversal
+        Node* const headp = *--topp;
+
+        // Prefetch in case we are ascending the tree
+        ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
+
+        // Ensure we have stack space for nextp and the 4 children
+        if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
+
+        // Enqueue the next node
+        if (headp->nextp()) *topp++ = headp->nextp();
+
+        // Visit the head node
+        visit(headp);
     }
 }
 
 // predicate implementation
-template 
-bool AstNode::predicateImpl(ConstCorrectAstNode* nodep,
-                            const std::function& p) {
-    // Implementation similar to foreach, but abort traversal as soon as result is determined.
-    if (!p) {
-        nodep->v3fatal("AstNode::foreach called with unbound function");  // LCOV_EXCL_LINE
-    } else {
-        using T_Arg_NonConst = typename std::remove_const::type;
-        using Node = ConstCorrectAstNode;
+template 
+bool AstNode::predicateImpl(ConstCorrectAstNode* nodep, const Callable& p) {
+    // Implementation similar to foreach, but abort traversal as soon as result is determined
+    using T_Arg_NonConst = typename std::remove_const::type;
+    using Node = ConstCorrectAstNode;
 
-        // Traversal stack
-        std::vector stack;  // Kept as a vector for easy resizing
-        Node** basep = nullptr;  // Pointer to base of stack
-        Node** topp = nullptr;  // Pointer to top of stack
-        Node** limp = nullptr;  // Pointer to stack limit (when need growing)
+    // Traversal stack
+    std::vector stack;  // Kept as a vector for easy resizing
+    Node** basep = nullptr;  // Pointer to base of stack
+    Node** topp = nullptr;  // Pointer to top of stack
+    Node** limp = nullptr;  // Pointer to stack limit (when need growing)
 
-        // We prefetch this far into the stack
-        constexpr int prefetchDistance = 2;
+    // We prefetch this far into the stack
+    constexpr int prefetchDistance = 2;
 
-        // Grow stack to given size
-        const auto grow = [&](size_t size) {
-            const ptrdiff_t occupancy = topp - basep;
-            stack.resize(size);
-            basep = stack.data() + prefetchDistance;
-            topp = basep + occupancy;
-            limp = basep + size - 5;  // We push max 5 items per iteration
-        };
+    // Grow stack to given size
+    const auto grow = [&](size_t size) {
+        const ptrdiff_t occupancy = topp - basep;
+        stack.resize(size);
+        basep = stack.data() + prefetchDistance;
+        topp = basep + occupancy;
+        limp = basep + size - 5;  // We push max 5 items per iteration
+    };
 
-        // Initial stack size
-        grow(32);
+    // Initial stack size
+    grow(32);
 
-        // We want some non-null pointers at the beginning. These will be prefetched, but not
-        // visited, so the root node will suffice. This eliminates needing branches in the loop.
-        for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
+    // We want some non-null pointers at the beginning. These will be prefetched, but not
+    // visited, so the root node will suffice. This eliminates needing branches in the loop.
+    for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
 
-        // Visit given node, enqueue children for traversal, return true if result determined.
-        const auto visit = [&](Node* currp) {
-            // Type test this node
-            if (AstNode::privateTypeTest(currp)) {
-                // Call the client function
-                if (p(static_cast(currp)) != Default) return true;
-                // Short circuit if iterating leaf nodes
-                if VL_CONSTEXPR_CXX17 (isLeaf()) return false;
-            }
-
-            // Enqueue children for traversal, unless futile
-            if (mayBeUnder(currp)) {
-                if (AstNode* const op4p = currp->op4p()) *topp++ = op4p;
-                if (AstNode* const op3p = currp->op3p()) *topp++ = op3p;
-                if (AstNode* const op2p = currp->op2p()) *topp++ = op2p;
-                if (AstNode* const op1p = currp->op1p()) *topp++ = op1p;
-            }
-
-            return false;
-        };
-
-        // Visit the root node
-        if (visit(nodep)) return !Default;
-
-        // Visit the rest of the tree
-        while (VL_LIKELY(topp > basep)) {
-            // Pop next node in the traversal
-            Node* const headp = *--topp;
-
-            // Prefetch in case we are ascending the tree
-            ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
-
-            // Ensure we have stack space for nextp and the 4 children
-            if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
-
-            // Enqueue the next node
-            if (headp->nextp()) *topp++ = headp->nextp();
-
-            // Visit the head node
-            if (visit(headp)) return !Default;
+    // Visit given node, enqueue children for traversal, return true if result determined.
+    const auto visit = [&](Node* currp) {
+        // Type test this node
+        if (AstNode::privateTypeTest(currp)) {
+            // Call the client function
+            if (p(static_cast(currp)) != Default) return true;
+            // Short circuit if iterating leaf nodes
+            if VL_CONSTEXPR_CXX17 (isLeaf()) return false;
         }
 
-        return Default;
+        // Enqueue children for traversal, unless futile
+        if (mayBeUnder(currp)) {
+            if (AstNode* const op4p = currp->op4p()) *topp++ = op4p;
+            if (AstNode* const op3p = currp->op3p()) *topp++ = op3p;
+            if (AstNode* const op2p = currp->op2p()) *topp++ = op2p;
+            if (AstNode* const op1p = currp->op1p()) *topp++ = op1p;
+        }
+
+        return false;
+    };
+
+    // Visit the root node
+    if (visit(nodep)) return !Default;
+
+    // Visit the rest of the tree
+    while (VL_LIKELY(topp > basep)) {
+        // Pop next node in the traversal
+        Node* const headp = *--topp;
+
+        // Prefetch in case we are ascending the tree
+        ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
+
+        // Ensure we have stack space for nextp and the 4 children
+        if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
+
+        // Enqueue the next node
+        if (headp->nextp()) *topp++ = headp->nextp();
+
+        // Visit the head node
+        if (visit(headp)) return !Default;
     }
+
+    return Default;
 }
 
 inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) {
@@ -2354,7 +2443,7 @@ public:
     VNRef(U&& x)
         : std::reference_wrapper{x} {}
 
-    VNRef(const VNRef& other) noexcept
+    VNRef(const std::reference_wrapper& other)
         : std::reference_wrapper{other} {}
 };
 
@@ -2408,7 +2497,7 @@ AstNode* VNVisitor::iterateSubtreeReturnEdits(AstNode* nodep) {
     return nodep->iterateSubtreeReturnEdits(*this);
 }
 
-// Include macros generated by 'astgen'. These include ASTGEN_MEMBERS_
+// Include macros generated by 'astgen'. These include ASTGEN_MEMBERS_Ast
 // for each AstNode sub-type, and ASTGEN_SUPER_ for concrete final
 // AstNode sub-types. The generated members include boilerplate methods related
 // to cloning, visitor dispatch, and other functionality. ASTGEN_SUPER_
diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h
index 198ed0ec5..144419d05 100644
--- a/src/V3AstInlines.h
+++ b/src/V3AstInlines.h
@@ -33,10 +33,10 @@ bool AstNode::width1() const {  // V3Const uses to know it can optimize
 int AstNode::widthInstrs() const {
     return (!dtypep() ? 1 : (dtypep()->isWide() ? dtypep()->widthWords() : 1));
 }
-bool AstNode::isDouble() const {
+bool AstNode::isDouble() const VL_MT_SAFE {
     return dtypep() && VN_IS(dtypep(), BasicDType) && VN_AS(dtypep(), BasicDType)->isDouble();
 }
-bool AstNode::isString() const {
+bool AstNode::isString() const VL_MT_SAFE {
     return dtypep() && dtypep()->basicp() && dtypep()->basicp()->isString();
 }
 bool AstNode::isSigned() const { return dtypep() && dtypep()->isSigned(); }
@@ -61,12 +61,12 @@ bool AstNode::sameGateTree(const AstNode* node2p) const {
     return sameTreeIter(this, node2p, true, true);
 }
 
-int AstNodeArrayDType::left() const { return rangep()->leftConst(); }
-int AstNodeArrayDType::right() const { return rangep()->rightConst(); }
-int AstNodeArrayDType::hi() const { return rangep()->hiConst(); }
-int AstNodeArrayDType::lo() const { return rangep()->loConst(); }
-int AstNodeArrayDType::elementsConst() const { return rangep()->elementsConst(); }
-VNumRange AstNodeArrayDType::declRange() const { return VNumRange{left(), right()}; }
+int AstNodeArrayDType::left() const VL_MT_SAFE { return rangep()->leftConst(); }
+int AstNodeArrayDType::right() const VL_MT_SAFE { return rangep()->rightConst(); }
+int AstNodeArrayDType::hi() const VL_MT_SAFE { return rangep()->hiConst(); }
+int AstNodeArrayDType::lo() const VL_MT_SAFE { return rangep()->loConst(); }
+int AstNodeArrayDType::elementsConst() const VL_MT_SAFE { return rangep()->elementsConst(); }
+VNumRange AstNodeArrayDType::declRange() const VL_MT_SAFE { return VNumRange{left(), right()}; }
 
 AstRange::AstRange(FileLine* fl, int left, int right)
     : ASTGEN_SUPER_Range(fl) {
@@ -87,7 +87,7 @@ int AstRange::rightConst() const {
     return (constp ? constp->toSInt() : 0);
 }
 
-int AstQueueDType::boundConst() const {
+int AstQueueDType::boundConst() const VL_MT_SAFE {
     AstConst* const constp = VN_CAST(boundp(), Const);
     return (constp ? constp->toSInt() : 0);
 }
@@ -99,13 +99,6 @@ AstPin::AstPin(FileLine* fl, int pinNum, AstVarRef* varname, AstNode* exprp)
     this->exprp(exprp);
 }
 
-AstDpiExportUpdated::AstDpiExportUpdated(FileLine* fl, AstVarScope* varScopep)
-    : ASTGEN_SUPER_DpiExportUpdated(fl) {
-    this->varRefp(new AstVarRef{fl, varScopep, VAccess::WRITE});
-}
-
-AstVarScope* AstDpiExportUpdated::varScopep() const { return varRefp()->varScopep(); }
-
 AstPackArrayDType::AstPackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp,
                                      AstRange* rangep)
     : ASTGEN_SUPER_PackArrayDType(fl) {
@@ -134,9 +127,8 @@ bool AstBasicDType::littleEndian() const {
     return (rangep() ? rangep()->littleEndian() : m.m_nrange.littleEndian());
 }
 
-bool AstActive::hasInitial() const { return m_sensesp->hasInitial(); }
-bool AstActive::hasSettle() const { return m_sensesp->hasSettle(); }
 bool AstActive::hasClocked() const { return m_sensesp->hasClocked(); }
+bool AstActive::hasCombo() const { return m_sensesp->hasCombo(); }
 
 AstElabDisplay::AstElabDisplay(FileLine* fl, VDisplayType dispType, AstNode* exprsp)
     : ASTGEN_SUPER_ElabDisplay(fl) {
diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h
index 4da216427..010a78d20 100644
--- a/src/V3AstNodeDType.h
+++ b/src/V3AstNodeDType.h
@@ -50,7 +50,7 @@ protected:
         : AstNode{t, fl} {}
 
 public:
-    ASTGEN_MEMBERS_NodeDType;
+    ASTGEN_MEMBERS_AstNodeDType;
     // ACCESSORS
     void dump(std::ostream& str) const override;
     virtual void dumpSmall(std::ostream& str) const;
@@ -97,25 +97,26 @@ public:
         m_widthMin = widthMin;
     }
     // For backward compatibility inherit width and signing from the subDType/base type
-    void widthFromSub(AstNodeDType* nodep) {
+    void widthFromSub(const AstNodeDType* nodep) {
         m_width = nodep->m_width;
         m_widthMin = nodep->m_widthMin;
         m_numeric = nodep->m_numeric;
     }
     //
-    int width() const { return m_width; }
+    int width() const VL_MT_SAFE { return m_width; }
     void numeric(VSigning flag) { m_numeric = flag; }
-    bool isSigned() const { return m_numeric.isSigned(); }
-    bool isNosign() const { return m_numeric.isNosign(); }
+    bool isSigned() const VL_MT_SAFE { return m_numeric.isSigned(); }
+    bool isNosign() const VL_MT_SAFE { return m_numeric.isNosign(); }
     VSigning numeric() const { return m_numeric; }
-    int widthWords() const { return VL_WORDS_I(width()); }
-    int widthMin() const {  // If sized, the size, if unsized the min digits to represent it
+    int widthWords() const VL_MT_SAFE { return VL_WORDS_I(width()); }
+    int widthMin() const VL_MT_SAFE {  // If sized, the size,
+                                       // if unsized the min digits to represent it
         return m_widthMin ? m_widthMin : m_width;
     }
     int widthPow2() const;
     void widthMinFromWidth() { m_widthMin = m_width; }
-    bool widthSized() const { return !m_widthMin || m_widthMin == m_width; }
-    bool generic() const { return m_generic; }
+    bool widthSized() const VL_MT_SAFE { return !m_widthMin || m_widthMin == m_width; }
+    bool generic() const VL_MT_SAFE { return m_generic; }
     void generic(bool flag) { m_generic = flag; }
     std::pair dimensions(bool includeBasic);
     uint32_t arrayUnpackedElements();  // 1, or total multiplication of all dimensions
@@ -123,12 +124,12 @@ public:
     const char* charIQWN() const {
         return (isString() ? "N" : isWide() ? "W" : isQuad() ? "Q" : "I");
     }
-    string cType(const string& name, bool forFunc, bool isRef) const;
-    bool isLiteralType() const;  // Does this represent a C++ LiteralType? (can be constexpr)
+    string cType(const string& name, bool forFunc, bool isRef) const VL_MT_SAFE;
+    bool isLiteralType() const VL_MT_SAFE;  // Represents a C++ LiteralType? (can be constexpr)
 
 private:
     class CTypeRecursed;
-    CTypeRecursed cTypeRecurse(bool compound) const;
+    CTypeRecursed cTypeRecurse(bool compound) const VL_MT_SAFE;
 };
 class AstNodeArrayDType VL_NOT_FINAL : public AstNodeDType {
     // Array data type, ie "some_dtype var_name [2:0]"
@@ -144,7 +145,7 @@ protected:
         : AstNodeDType{t, fl} {}
 
 public:
-    ASTGEN_MEMBERS_NodeArrayDType;
+    ASTGEN_MEMBERS_AstNodeArrayDType;
     void dump(std::ostream& str) const override;
     void dumpSmall(std::ostream& str) const override;
     const char* broken() const override {
@@ -167,15 +168,17 @@ public:
                 && subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp()));
     }
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
-    AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); }
+    AstNodeDType* subDTypep() const override VL_MT_SAFE {
+        return m_refDTypep ? m_refDTypep : childDTypep();
+    }
     void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
     AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
     void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
     // METHODS
-    AstBasicDType* basicp() const override {
+    AstBasicDType* basicp() const override VL_MT_SAFE {
         return subDTypep()->basicp();
     }  // (Slow) recurse down to find basic data type
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
@@ -212,7 +215,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeUOrStructDType;
+    ASTGEN_MEMBERS_AstNodeUOrStructDType;
     int uniqueNum() const { return m_uniqueNum; }
     const char* broken() const override;
     void dump(std::ostream& str) const override;
@@ -225,7 +228,7 @@ public:
                     : VN_AS(findBitRangeDType(VNumRange{width() - 1, 0}, width(), numeric()),
                             BasicDType));
     }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     // (Slow) recurses - Structure alignment 1,2,4 or 8 bytes (arrays affect this)
@@ -237,7 +240,7 @@ public:
     }
     string name() const override { return m_name; }
     void name(const string& flag) override { m_name = flag; }
-    bool packed() const { return m_packed; }
+    bool packed() const VL_MT_SAFE { return m_packed; }
     // packed() but as don't support unpacked, presently all structs
     static bool packedUnsup() { return true; }
     void isFourstate(bool flag) { m_isFourstate = flag; }
@@ -270,7 +273,7 @@ public:
         this->rangep(rangep);
         this->valuep(valuep);
     }
-    ASTGEN_MEMBERS_EnumItem;
+    ASTGEN_MEMBERS_AstEnumItem;
     string name() const override { return m_name; }
     bool maybePointedTo() const override { return true; }
     bool hasDType() const override { return true; }
@@ -300,7 +303,7 @@ public:
         keyDTypep(keyDtp);
         dtypep(dtp);
     }
-    ASTGEN_MEMBERS_AssocArrayDType;
+    ASTGEN_MEMBERS_AstAssocArrayDType;
     const char* broken() const override {
         BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
                      || (!m_refDTypep && childDTypep())));
@@ -327,18 +330,22 @@ public:
     void dumpSmall(std::ostream& str) const override;
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
     AstNodeDType* getChild2DTypep() const override { return keyChildDTypep(); }
-    AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); }
+    AstNodeDType* subDTypep() const override VL_MT_SAFE {
+        return m_refDTypep ? m_refDTypep : childDTypep();
+    }
     void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
     AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
     void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
     AstNodeDType* virtRefDType2p() const override { return m_keyDTypep; }
     void virtRefDType2p(AstNodeDType* nodep) override { keyDTypep(nodep); }
     //
-    AstNodeDType* keyDTypep() const { return m_keyDTypep ? m_keyDTypep : keyChildDTypep(); }
+    AstNodeDType* keyDTypep() const VL_MT_SAFE {
+        return m_keyDTypep ? m_keyDTypep : keyChildDTypep();
+    }
     void keyDTypep(AstNodeDType* nodep) { m_keyDTypep = nodep; }
     // METHODS
-    AstBasicDType* basicp() const override { return nullptr; }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
@@ -385,7 +392,7 @@ private:
               AstRange* rangep);
 
 public:
-    ASTGEN_MEMBERS_BasicDType;
+    ASTGEN_MEMBERS_AstBasicDType;
     void dump(std::ostream& str) const override;
     // width/widthMin/numeric compared elsewhere
     bool same(const AstNode* samep) const override {
@@ -410,8 +417,8 @@ public:
         }
     }
     // METHODS
-    AstBasicDType* basicp() const override { return (AstBasicDType*)this; }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return (AstBasicDType*)this; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     // (Slow) recurses - Structure alignment 1,2,4 or 8 bytes (arrays affect this)
@@ -419,14 +426,25 @@ public:
     // (Slow) recurses - Width in bytes rounding up 1,2,4,8,12,...
     int widthTotalBytes() const override;
     bool isFourstate() const override { return keyword().isFourstate(); }
-    VBasicDTypeKwd keyword() const {  // Avoid using - use isSomething accessors instead
+    VBasicDTypeKwd keyword() const VL_MT_SAFE {  // Avoid using - use isSomething accessors instead
         return m.m_keyword;
     }
     bool isBitLogic() const { return keyword().isBitLogic(); }
-    bool isDouble() const { return keyword().isDouble(); }
-    bool isEventValue() const { return keyword().isEventValue(); }
-    bool isOpaque() const { return keyword().isOpaque(); }
-    bool isString() const { return keyword().isString(); }
+    bool isDouble() const VL_MT_SAFE { return keyword().isDouble(); }
+    bool isEvent() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::EVENT; }
+    bool isTriggerVec() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::TRIGGERVEC; }
+    bool isForkSync() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::FORK_SYNC; }
+    bool isDelayScheduler() const VL_MT_SAFE {
+        return keyword() == VBasicDTypeKwd::DELAY_SCHEDULER;
+    }
+    bool isTriggerScheduler() const VL_MT_SAFE {
+        return keyword() == VBasicDTypeKwd::TRIGGER_SCHEDULER;
+    }
+    bool isDynamicTriggerScheduler() const VL_MT_SAFE {
+        return keyword() == VBasicDTypeKwd::DYNAMIC_TRIGGER_SCHEDULER;
+    }
+    bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); }
+    bool isString() const VL_MT_SAFE { return keyword().isString(); }
     bool isZeroInit() const { return keyword().isZeroInit(); }
     bool isRanged() const { return rangep() || m.m_nrange.ranged(); }
     bool isDpiBitVec() const {  // DPI uses svBitVecVal
@@ -463,15 +481,15 @@ public:
         this->childDTypep(childDTypep);
         this->elementsp(elementsp);
     }
-    ASTGEN_MEMBERS_BracketArrayDType;
+    ASTGEN_MEMBERS_AstBracketArrayDType;
     bool similarDType(AstNodeDType* samep) const override { V3ERROR_NA_RETURN(false); }
     AstNodeDType* subDTypep() const override { return childDTypep(); }
     // METHODS
     // Will be removed in V3Width, which relies on this
     // being a child not a dtype pointed node
     bool maybePointedTo() const override { return false; }
-    AstBasicDType* basicp() const override { return nullptr; }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { V3ERROR_NA_RETURN(0); }
@@ -491,7 +509,7 @@ public:
         this->dtypep(this);
         this->addParamsp(paramsp);
     }
-    ASTGEN_MEMBERS_ClassRefDType;
+    ASTGEN_MEMBERS_AstClassRefDType;
     // METHODS
     const char* broken() const override;
     void cloneRelink() override;
@@ -505,8 +523,8 @@ public:
     void dump(std::ostream& str = std::cout) const override;
     void dumpSmall(std::ostream& str) const override;
     string name() const override;
-    AstBasicDType* basicp() const override { return nullptr; }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { return 0; }
@@ -535,7 +553,7 @@ public:
         dtypep(nullptr);  // V3Width will resolve
         widthFromSub(subDTypep());
     }
-    ASTGEN_MEMBERS_ConstDType;
+    ASTGEN_MEMBERS_AstConstDType;
     const char* broken() const override {
         BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
                      || (!m_refDTypep && childDTypep())));
@@ -557,8 +575,8 @@ public:
     AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
     void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
     // METHODS
-    AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
-    AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return subDTypep()->skipRefToEnump(); }
     int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
@@ -589,7 +607,7 @@ public:
         childDTypep(dtp);  // Only for parser
         dtypep(nullptr);  // V3Width will resolve
     }
-    ASTGEN_MEMBERS_DefImplicitDType;
+    ASTGEN_MEMBERS_AstDefImplicitDType;
     int uniqueNum() const { return m_uniqueNum; }
     bool same(const AstNode* samep) const override {
         const AstDefImplicitDType* const sp = static_cast(samep);
@@ -604,8 +622,8 @@ public:
     // METHODS
     // op1 = Range of variable
     AstNodeDType* dtypeSkipRefp() const { return dtypep()->skipRefp(); }
-    AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { return dtypep()->widthAlignBytes(); }
@@ -631,7 +649,7 @@ public:
         refDTypep(dtp);
         dtypep(nullptr);  // V3Width will resolve
     }
-    ASTGEN_MEMBERS_DynArrayDType;
+    ASTGEN_MEMBERS_AstDynArrayDType;
     const char* broken() const override {
         BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
                      || (!m_refDTypep && childDTypep())));
@@ -653,13 +671,15 @@ public:
     string prettyDTypeName() const override;
     void dumpSmall(std::ostream& str) const override;
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
-    AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); }
+    AstNodeDType* subDTypep() const override VL_MT_SAFE {
+        return m_refDTypep ? m_refDTypep : childDTypep();
+    }
     void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
     AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
     void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
     // METHODS
-    AstBasicDType* basicp() const override { return nullptr; }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
@@ -673,7 +693,7 @@ public:
         : ASTGEN_SUPER_EmptyQueueDType(fl) {
         dtypep(this);
     }
-    ASTGEN_MEMBERS_EmptyQueueDType;
+    ASTGEN_MEMBERS_AstEmptyQueueDType;
     void dumpSmall(std::ostream& str) const override;
     bool hasDType() const override { return true; }
     bool maybePointedTo() const override { return true; }
@@ -682,9 +702,9 @@ public:
     AstNodeDType* virtRefDTypep() const override { return nullptr; }
     void virtRefDTypep(AstNodeDType* nodep) override {}
     bool similarDType(AstNodeDType* samep) const override { return this == samep; }
-    AstBasicDType* basicp() const override { return nullptr; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
     // cppcheck-suppress csyleCast
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     // cppcheck-suppress csyleCast
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     // cppcheck-suppress csyleCast
@@ -712,7 +732,7 @@ public:
         dtypep(nullptr);  // V3Width will resolve
         widthFromSub(subDTypep());
     }
-    ASTGEN_MEMBERS_EnumDType;
+    ASTGEN_MEMBERS_AstEnumDType;
     const char* broken() const override {
         BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
                      || (!m_refDTypep && childDTypep())));
@@ -735,8 +755,8 @@ public:
     string name() const override { return m_name; }
     void name(const string& flag) override { m_name = flag; }
     // METHODS
-    AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
-    AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); }
     AstNodeDType* skipRefToConstp() const override { return subDTypep()->skipRefToConstp(); }
     // cppcheck-suppress csyleCast
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
@@ -751,6 +771,7 @@ public:
 };
 class AstIfaceRefDType final : public AstNodeDType {
     // Reference to an interface, either for a port, or inside parent cell
+    // @astgen op1 := paramsp : List[AstPin]
 private:
     FileLine* m_modportFileline;  // Where modport token was
     string m_cellName;  // "" = no cell, such as when connects to 'input' iface
@@ -773,14 +794,22 @@ public:
         , m_cellName{cellName}
         , m_ifaceName{ifaceName}
         , m_modportName{modport} {}
-    ASTGEN_MEMBERS_IfaceRefDType;
+    AstIfaceRefDType(FileLine* fl, FileLine* modportFl, const string& cellName,
+                     const string& ifaceName, const string& modport, AstPin* paramsp)
+        : ASTGEN_SUPER_IfaceRefDType(fl)
+        , m_modportFileline{modportFl}
+        , m_cellName{cellName}
+        , m_ifaceName{ifaceName} {
+        addParamsp(paramsp);
+    }
+    ASTGEN_MEMBERS_AstIfaceRefDType;
     // METHODS
     const char* broken() const override;
     void dump(std::ostream& str = std::cout) const override;
     void dumpSmall(std::ostream& str) const override;
     void cloneRelink() override;
-    AstBasicDType* basicp() const override { return nullptr; }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     bool similarDType(AstNodeDType* samep) const override { return this == samep; }
@@ -828,7 +857,7 @@ public:
         dtypep(this);
         widthFromSub(subDTypep());
     }
-    ASTGEN_MEMBERS_MemberDType;
+    ASTGEN_MEMBERS_AstMemberDType;
     string name() const override { return m_name; }  // * = Var name
     bool hasDType() const override { return true; }
     bool maybePointedTo() const override { return true; }
@@ -848,10 +877,10 @@ public:
     //
     // (Slow) recurse down to find basic data type (Note don't need virtual -
     // AstVar isn't a NodeDType)
-    AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); }
     // op1 = Range of variable (Note don't need virtual - AstVar isn't a NodeDType)
     AstNodeDType* dtypeSkipRefp() const { return subDTypep()->skipRefp(); }
-    AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); }
     AstNodeDType* skipRefToConstp() const override { return subDTypep()->skipRefToConstp(); }
     AstNodeDType* skipRefToEnump() const override { return subDTypep()->skipRefToEnump(); }
     // (Slow) recurses - Structure alignment 1,2,4 or 8 bytes (arrays affect this)
@@ -885,11 +914,12 @@ public:
         childDTypep(dtp);  // Only for parser
         dtypep(nullptr);  // V3Width will resolve
     }
-    ASTGEN_MEMBERS_ParamTypeDType;
+    ASTGEN_MEMBERS_AstParamTypeDType;
+    void dump(std::ostream& str = std::cout) const override;
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
     AstNodeDType* subDTypep() const override { return dtypep() ? dtypep() : childDTypep(); }
-    AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
-    AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); }
     AstNodeDType* skipRefToConstp() const override { return subDTypep()->skipRefToConstp(); }
     AstNodeDType* skipRefToEnump() const override { return subDTypep()->skipRefToEnump(); }
     bool similarDType(AstNodeDType* samep) const override {
@@ -919,12 +949,12 @@ class AstParseTypeDType final : public AstNodeDType {
 public:
     explicit AstParseTypeDType(FileLine* fl)
         : ASTGEN_SUPER_ParseTypeDType(fl) {}
-    ASTGEN_MEMBERS_ParseTypeDType;
+    ASTGEN_MEMBERS_AstParseTypeDType;
     AstNodeDType* dtypep() const { return nullptr; }
     // METHODS
     bool similarDType(AstNodeDType* samep) const override { return this == samep; }
-    AstBasicDType* basicp() const override { return nullptr; }
-    AstNodeDType* skipRefp() const override { return nullptr; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return nullptr; }
     // cppcheck-suppress csyleCast
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     // cppcheck-suppress csyleCast
@@ -956,7 +986,7 @@ public:
         refDTypep(dtp);
         dtypep(dtp);
     }
-    ASTGEN_MEMBERS_QueueDType;
+    ASTGEN_MEMBERS_AstQueueDType;
     const char* broken() const override {
         BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
                      || (!m_refDTypep && childDTypep())));
@@ -978,15 +1008,17 @@ public:
     void dumpSmall(std::ostream& str) const override;
     string prettyDTypeName() const override;
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
-    AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); }
+    AstNodeDType* subDTypep() const override VL_MT_SAFE {
+        return m_refDTypep ? m_refDTypep : childDTypep();
+    }
     void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
     inline int boundConst() const;
     AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
     void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
     // METHODS
-    AstBasicDType* basicp() const override { return nullptr; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
     // cppcheck-suppress csyleCast
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     // cppcheck-suppress csyleCast
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     // cppcheck-suppress csyleCast
@@ -1022,7 +1054,7 @@ public:
         : ASTGEN_SUPER_RefDType(fl) {
         this->typeofp(typeofp);
     }
-    ASTGEN_MEMBERS_RefDType;
+    ASTGEN_MEMBERS_AstRefDType;
     // METHODS
     const char* broken() const override;
     void cloneRelink() override;
@@ -1039,11 +1071,11 @@ public:
     string prettyDTypeName() const override {
         return subDTypep() ? subDTypep()->name() : prettyName();
     }
-    AstBasicDType* basicp() const override {
+    AstBasicDType* basicp() const override VL_MT_SAFE {
         return subDTypep() ? subDTypep()->basicp() : nullptr;
     }
     AstNodeDType* subDTypep() const override;
-    AstNodeDType* skipRefp() const override {
+    AstNodeDType* skipRefp() const override VL_MT_SAFE {
         // Skip past both the Ref and the Typedef
         if (subDTypep()) {
             return subDTypep()->skipRefp();
@@ -1097,7 +1129,7 @@ public:
         refDTypep(nullptr);
         dtypep(nullptr);  // V3Width will resolve
     }
-    ASTGEN_MEMBERS_UnsizedArrayDType;
+    ASTGEN_MEMBERS_AstUnsizedArrayDType;
     const char* broken() const override {
         BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
                      || (!m_refDTypep && childDTypep())));
@@ -1115,8 +1147,8 @@ public:
     AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
     void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
     // METHODS
-    AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); }
@@ -1130,7 +1162,7 @@ public:
         : ASTGEN_SUPER_VoidDType(fl) {
         dtypep(this);
     }
-    ASTGEN_MEMBERS_VoidDType;
+    ASTGEN_MEMBERS_AstVoidDType;
     void dumpSmall(std::ostream& str) const override;
     bool hasDType() const override { return true; }
     bool maybePointedTo() const override { return true; }
@@ -1139,9 +1171,9 @@ public:
     AstNodeDType* virtRefDTypep() const override { return nullptr; }
     void virtRefDTypep(AstNodeDType* nodep) override {}
     bool similarDType(AstNodeDType* samep) const override { return this == samep; }
-    AstBasicDType* basicp() const override { return nullptr; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; }
     // cppcheck-suppress csyleCast
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     // cppcheck-suppress csyleCast
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     // cppcheck-suppress csyleCast
@@ -1162,7 +1194,7 @@ public:
         refDTypep(nullptr);
         dtypep(nullptr);  // V3Width will resolve
     }
-    ASTGEN_MEMBERS_WildcardArrayDType;
+    ASTGEN_MEMBERS_AstWildcardArrayDType;
     const char* broken() const override {
         BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
                      || (!m_refDTypep && childDTypep())));
@@ -1175,13 +1207,15 @@ public:
     bool similarDType(AstNodeDType* samep) const override;
     void dumpSmall(std::ostream& str) const override;
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
-    AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); }
+    AstNodeDType* subDTypep() const override VL_MT_SAFE {
+        return m_refDTypep ? m_refDTypep : childDTypep();
+    }
     void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
     AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
     void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
     // METHODS
-    AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
-    AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
+    AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); }
+    AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
     AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
     int widthAlignBytes() const override { return sizeof(std::map); }
@@ -1195,7 +1229,7 @@ class AstPackArrayDType final : public AstNodeArrayDType {
 public:
     inline AstPackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstRange* rangep);
     inline AstPackArrayDType(FileLine* fl, AstNodeDType* dtp, AstRange* rangep);
-    ASTGEN_MEMBERS_PackArrayDType;
+    ASTGEN_MEMBERS_AstPackArrayDType;
     string prettyDTypeName() const override;
     bool isCompound() const override { return false; }
 };
@@ -1222,7 +1256,7 @@ public:
         // width and signing from the subDType/base type
         widthFromSub(subDTypep());
     }
-    ASTGEN_MEMBERS_UnpackArrayDType;
+    ASTGEN_MEMBERS_AstUnpackArrayDType;
     string prettyDTypeName() const override;
     bool same(const AstNode* samep) const override {
         const AstUnpackArrayDType* const sp = static_cast(samep);
@@ -1231,7 +1265,7 @@ public:
     // Outer dimension comes first. The first element is this node.
     std::vector unpackDimensions();
     void isCompound(bool flag) { m_isCompound = flag; }
-    bool isCompound() const override { return m_isCompound; }
+    bool isCompound() const override VL_MT_SAFE { return m_isCompound; }
 };
 
 // === AstNodeUOrStructDType ===
@@ -1240,7 +1274,7 @@ public:
     // VSigning below is mispurposed to indicate if packed or not
     AstStructDType(FileLine* fl, VSigning numericUnpack)
         : ASTGEN_SUPER_StructDType(fl, numericUnpack) {}
-    ASTGEN_MEMBERS_StructDType;
+    ASTGEN_MEMBERS_AstStructDType;
     string verilogKwd() const override { return "struct"; }
 };
 class AstUnionDType final : public AstNodeUOrStructDType {
@@ -1249,7 +1283,7 @@ public:
     // VSigning below is mispurposed to indicate if packed or not
     AstUnionDType(FileLine* fl, VSigning numericUnpack)
         : ASTGEN_SUPER_UnionDType(fl, numericUnpack) {}
-    ASTGEN_MEMBERS_UnionDType;
+    ASTGEN_MEMBERS_AstUnionDType;
     string verilogKwd() const override { return "union"; }
 };
 
diff --git a/src/V3AstNodeMath.h b/src/V3AstNodeMath.h
index 977a49b96..ef635a14a 100644
--- a/src/V3AstNodeMath.h
+++ b/src/V3AstNodeMath.h
@@ -44,7 +44,7 @@ protected:
         : AstNode{t, fl} {}
 
 public:
-    ASTGEN_MEMBERS_NodeMath;
+    ASTGEN_MEMBERS_AstNodeMath;
     // METHODS
     void dump(std::ostream& str) const override;
     bool hasDType() const override { return true; }
@@ -70,7 +70,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeBiop;
+    ASTGEN_MEMBERS_AstNodeBiop;
     // Clone single node, just get same type back.
     virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) = 0;
     // METHODS
@@ -94,7 +94,7 @@ protected:
         : AstNodeBiop{t, fl, lhs, rhs} {}
 
 public:
-    ASTGEN_MEMBERS_NodeBiCom;
+    ASTGEN_MEMBERS_AstNodeBiCom;
 };
 class AstNodeBiComAsv VL_NOT_FINAL : public AstNodeBiCom {
     // Binary math with commutative & associative properties
@@ -103,7 +103,7 @@ protected:
         : AstNodeBiCom{t, fl, lhs, rhs} {}
 
 public:
-    ASTGEN_MEMBERS_NodeBiComAsv;
+    ASTGEN_MEMBERS_AstNodeBiComAsv;
 };
 class AstNodeSel VL_NOT_FINAL : public AstNodeBiop {
     // Single bit range extraction, perhaps with non-constant selection or array selection
@@ -114,7 +114,7 @@ protected:
         : AstNodeBiop{t, fl, fromp, bitp} {}
 
 public:
-    ASTGEN_MEMBERS_NodeSel;
+    ASTGEN_MEMBERS_AstNodeSel;
     int bitConst() const;
     bool hasDType() const override { return true; }
 };
@@ -127,7 +127,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeStream;
+    ASTGEN_MEMBERS_AstNodeStream;
 };
 class AstNodeSystemBiop VL_NOT_FINAL : public AstNodeBiop {
 public:
@@ -135,7 +135,7 @@ public:
         : AstNodeBiop(t, fl, lhsp, rhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_NodeSystemBiop;
+    ASTGEN_MEMBERS_AstNodeSystemBiop;
     bool cleanOut() const override { return false; }
     bool cleanLhs() const override { return false; }
     bool cleanRhs() const override { return false; }
@@ -161,7 +161,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeQuadop;
+    ASTGEN_MEMBERS_AstNodeQuadop;
     // METHODS
     // Set out to evaluation of a AstConst'ed
     virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
@@ -185,7 +185,7 @@ protected:
         : AstNodeMath{t, fl} {}
 
 public:
-    ASTGEN_MEMBERS_NodeTermop;
+    ASTGEN_MEMBERS_AstNodeTermop;
     // Know no children, and hot function, so skip iterator for speed
     // cppcheck-suppress functionConst
     void iterateChildren(VNVisitor& v) {}
@@ -205,7 +205,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeTriop;
+    ASTGEN_MEMBERS_AstNodeTriop;
     // METHODS
     void dump(std::ostream& str) const override;
     // Set out to evaluation of a AstConst'ed
@@ -236,7 +236,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeCond;
+    ASTGEN_MEMBERS_AstNodeCond;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
                        const V3Number& ths) override;
     string emitVerilog() override { return "%k(%l %f? %r %k: %t)"; }
@@ -262,7 +262,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeUniop;
+    ASTGEN_MEMBERS_AstNodeUniop;
     // METHODS
     void dump(std::ostream& str) const override;
     // Set out to evaluation of a AstConst'ed lhs
@@ -282,7 +282,7 @@ public:
         : AstNodeUniop(t, fl, lhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_NodeSystemUniop;
+    ASTGEN_MEMBERS_AstNodeSystemUniop;
     bool cleanOut() const override { return true; }
     bool cleanLhs() const override { return false; }
     bool sizeMattersLhs() const override { return false; }
@@ -315,7 +315,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeVarRef;
+    ASTGEN_MEMBERS_AstNodeVarRef;
     void dump(std::ostream& str) const override;
     bool hasDType() const override { return true; }
     const char* broken() const override;
@@ -358,7 +358,7 @@ public:
     }
 
 public:
-    ASTGEN_MEMBERS_AddrOfCFunc;
+    ASTGEN_MEMBERS_AstAddrOfCFunc;
     void cloneRelink() override;
     const char* broken() const override;
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
@@ -381,7 +381,7 @@ public:
         dtypeFrom(exprsp);
     }
     inline AstCMath(FileLine* fl, const string& textStmt, int setwidth, bool cleanOut = true);
-    ASTGEN_MEMBERS_CMath;
+    ASTGEN_MEMBERS_AstCMath;
     bool isGateOptimizable() const override { return m_pure; }
     bool isPredictOptimizable() const override { return m_pure; }
     bool cleanOut() const override { return m_cleanOut; }
@@ -399,7 +399,7 @@ public:
         : ASTGEN_SUPER_ConsAssoc(fl) {
         this->defaultp(defaultp);
     }
-    ASTGEN_MEMBERS_ConsAssoc;
+    ASTGEN_MEMBERS_AstConsAssoc;
     string emitVerilog() override { return "'{}"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -417,7 +417,7 @@ public:
         this->lhsp(lhsp);
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_ConsDynArray;
+    ASTGEN_MEMBERS_AstConsDynArray;
     string emitVerilog() override { return "'{%l, %r}"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -435,7 +435,7 @@ public:
         this->lhsp(lhsp);
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_ConsQueue;
+    ASTGEN_MEMBERS_AstConsQueue;
     string emitVerilog() override { return "'{%l, %r}"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -451,7 +451,7 @@ public:
         : ASTGEN_SUPER_ConsWildcard(fl) {
         this->defaultp(defaultp);
     }
-    ASTGEN_MEMBERS_ConsWildcard;
+    ASTGEN_MEMBERS_AstConsWildcard;
     string emitVerilog() override { return "'{}"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -572,12 +572,12 @@ public:
         dtypeSetBit();  // Events 1 bit, objects 64 bits, so autoExtend=1 and use bit here
         initWithNumber();
     }
-    ASTGEN_MEMBERS_Const;
+    ASTGEN_MEMBERS_AstConst;
     string name() const override { return num().ascii(); }  // * = Value
-    const V3Number& num() const { return m_num; }  // * = Value
+    const V3Number& num() const VL_MT_SAFE { return m_num; }  // * = Value
     V3Number& num() { return m_num; }  // * = Value
     uint32_t toUInt() const { return num().toUInt(); }
-    int32_t toSInt() const { return num().toSInt(); }
+    int32_t toSInt() const VL_MT_SAFE { return num().toSInt(); }
     uint64_t toUQuad() const { return num().toUQuad(); }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -597,7 +597,7 @@ class AstEmptyQueue final : public AstNodeMath {
 public:
     explicit AstEmptyQueue(FileLine* fl)
         : ASTGEN_SUPER_EmptyQueue(fl) {}
-    ASTGEN_MEMBERS_EmptyQueue;
+    ASTGEN_MEMBERS_AstEmptyQueue;
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitVerilog() override { return "{}"; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -614,7 +614,7 @@ public:
         , m_classOrPackagep{classOrPackagep} {
         dtypeFrom(m_itemp);
     }
-    ASTGEN_MEMBERS_EnumItemRef;
+    ASTGEN_MEMBERS_AstEnumItemRef;
     void dump(std::ostream& str) const override;
     string name() const override { return itemp()->name(); }
     int instrCount() const override { return 0; }
@@ -646,7 +646,7 @@ public:
         this->resultp(resultp);
         dtypeFrom(resultp);
     }
-    ASTGEN_MEMBERS_ExprStmt;
+    ASTGEN_MEMBERS_AstExprStmt;
     // METHODS
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -662,7 +662,7 @@ public:
         this->filep(filep);
         this->strp(strp);
     }
-    ASTGEN_MEMBERS_FError;
+    ASTGEN_MEMBERS_AstFError;
     string emitVerilog() override { return "%f$ferror(%l, %r)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -685,7 +685,7 @@ public:
         this->startp(startp);
         this->countp(countp);
     }
-    ASTGEN_MEMBERS_FRead;
+    ASTGEN_MEMBERS_AstFRead;
     string verilogKwd() const override { return "$fread"; }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -704,7 +704,7 @@ public:
         : ASTGEN_SUPER_FRewind(fl) {
         this->filep(filep);
     }
-    ASTGEN_MEMBERS_FRewind;
+    ASTGEN_MEMBERS_AstFRewind;
     string verilogKwd() const override { return "$frewind"; }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -729,7 +729,7 @@ public:
         addExprsp(exprsp);
         this->filep(filep);
     }
-    ASTGEN_MEMBERS_FScanF;
+    ASTGEN_MEMBERS_AstFScanF;
     string name() const override { return m_text; }
     string verilogKwd() const override { return "$fscanf"; }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
@@ -756,7 +756,7 @@ public:
         this->offset(offset);
         this->operation(operation);
     }
-    ASTGEN_MEMBERS_FSeek;
+    ASTGEN_MEMBERS_AstFSeek;
     string verilogKwd() const override { return "$fseek"; }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -775,7 +775,7 @@ public:
         : ASTGEN_SUPER_FTell(fl) {
         this->filep(filep);
     }
-    ASTGEN_MEMBERS_FTell;
+    ASTGEN_MEMBERS_AstFTell;
     string verilogKwd() const override { return "$ftell"; }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -796,7 +796,7 @@ public:
         : ASTGEN_SUPER_Fell(fl) {
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_Fell;
+    ASTGEN_MEMBERS_AstFell;
     string emitVerilog() override { return "$fell(%l)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -814,7 +814,7 @@ public:
         this->exprp(exprp);
         this->rangep(rangep);
     }
-    ASTGEN_MEMBERS_GatePin;
+    ASTGEN_MEMBERS_AstGatePin;
     string emitVerilog() override { return "%l"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -830,7 +830,7 @@ public:
         this->lhsp(lhsp);
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_Implication;
+    ASTGEN_MEMBERS_AstImplication;
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -848,7 +848,7 @@ public:
         this->addItemsp(itemsp);
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_Inside;
+    ASTGEN_MEMBERS_AstInside;
     string emitVerilog() override { return "%l inside { %r }"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return false; }  // NA
@@ -862,7 +862,7 @@ public:
         this->lhsp(lhsp);
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_InsideRange;
+    ASTGEN_MEMBERS_AstInsideRange;
     string emitVerilog() override { return "[%l:%r]"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return false; }  // NA
@@ -882,7 +882,7 @@ public:
         : ASTGEN_SUPER_LambdaArgRef(fl)
         , m_name{name}
         , m_index(index) {}
-    ASTGEN_MEMBERS_LambdaArgRef;
+    ASTGEN_MEMBERS_AstLambdaArgRef;
     bool same(const AstNode* /*samep*/) const override { return true; }
     string emitVerilog() override { return name(); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -913,7 +913,7 @@ public:
         this->fromp(fromp);
         dtypep(dtp);
     }
-    ASTGEN_MEMBERS_MemberSel;
+    ASTGEN_MEMBERS_AstMemberSel;
     void cloneRelink() override;
     const char* broken() const override;
     void dump(std::ostream& str) const override;
@@ -936,7 +936,7 @@ public:
         dtypeFrom(rhsp);  // otherwise V3Width will resolve
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_NewCopy;
+    ASTGEN_MEMBERS_AstNewCopy;
     string emitVerilog() override { return "new"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -955,7 +955,7 @@ public:
         this->sizep(sizep);
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_NewDynamic;
+    ASTGEN_MEMBERS_AstNewDynamic;
     string emitVerilog() override { return "new"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -973,7 +973,7 @@ public:
         this->exprp(exprp);
         this->ticksp(ticksp);
     }
-    ASTGEN_MEMBERS_Past;
+    ASTGEN_MEMBERS_AstPast;
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -999,7 +999,7 @@ public:
         this->keyp(keyp);
         this->repp(repp);
     }
-    ASTGEN_MEMBERS_PatMember;
+    ASTGEN_MEMBERS_AstPatMember;
     string emitVerilog() override { return lhssp() ? "%f{%r{%k%l}}" : "%l"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -1020,7 +1020,7 @@ public:
         : ASTGEN_SUPER_Pattern(fl) {
         addItemsp(itemsp);
     }
-    ASTGEN_MEMBERS_Pattern;
+    ASTGEN_MEMBERS_AstPattern;
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -1048,7 +1048,7 @@ public:
         , m_urandom{urandom} {
         this->seedp(seedp);
     }
-    ASTGEN_MEMBERS_Rand;
+    ASTGEN_MEMBERS_AstRand;
     string emitVerilog() override {
         return seedp() ? (m_urandom ? "%f$urandom(%l)" : "%f$random(%l)")
                        : (m_urandom ? "%f$urandom()" : "%f$random()");
@@ -1081,7 +1081,7 @@ public:
         : ASTGEN_SUPER_Rose(fl) {
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_Rose;
+    ASTGEN_MEMBERS_AstRose;
     string emitVerilog() override { return "$rose(%l)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -1102,7 +1102,7 @@ public:
         this->addExprsp(exprsp);
         this->fromp(fromp);
     }
-    ASTGEN_MEMBERS_SScanF;
+    ASTGEN_MEMBERS_AstSScanF;
     string name() const override { return m_text; }
     string verilogKwd() const override { return "$sscanf"; }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
@@ -1126,7 +1126,7 @@ public:
         : ASTGEN_SUPER_Sampled(fl) {
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_Sampled;
+    ASTGEN_MEMBERS_AstSampled;
     string emitVerilog() override { return "$sampled(%l)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -1152,7 +1152,7 @@ public:
         , m_forFormat{forFormat} {
         dtypeSetUInt64();
     }
-    ASTGEN_MEMBERS_ScopeName;
+    ASTGEN_MEMBERS_AstScopeName;
     bool same(const AstNode* samep) const override {
         return (m_dpiExport == static_cast(samep)->m_dpiExport
                 && m_forFormat == static_cast(samep)->m_forFormat);
@@ -1190,7 +1190,7 @@ public:
         this->keyp(keyp);
         this->valuep(valuep);
     }
-    ASTGEN_MEMBERS_SetAssoc;
+    ASTGEN_MEMBERS_AstSetAssoc;
     string emitVerilog() override { return "'{}"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -1211,7 +1211,7 @@ public:
         this->keyp(keyp);
         this->valuep(valuep);
     }
-    ASTGEN_MEMBERS_SetWildcard;
+    ASTGEN_MEMBERS_AstSetWildcard;
     string emitVerilog() override { return "'{}"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -1228,7 +1228,7 @@ public:
         : ASTGEN_SUPER_Stable(fl) {
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_Stable;
+    ASTGEN_MEMBERS_AstStable;
     string emitVerilog() override { return "$stable(%l)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
@@ -1244,7 +1244,7 @@ public:
         : ASTGEN_SUPER_SystemF(fl) {
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_SystemF;
+    ASTGEN_MEMBERS_AstSystemF;
     string verilogKwd() const override { return "$system"; }
     string emitVerilog() override { return verilogKwd(); }
     string emitC() override { return "VL_SYSTEM_%nq(%lw, %P)"; }
@@ -1264,7 +1264,7 @@ public:
         : ASTGEN_SUPER_TestPlusArgs(fl) {
         this->searchp(searchp);
     }
-    ASTGEN_MEMBERS_TestPlusArgs;
+    ASTGEN_MEMBERS_AstTestPlusArgs;
     string verilogKwd() const override { return "$test$plusargs"; }
     string emitVerilog() override { return verilogKwd(); }
     string emitC() override { return "VL_VALUEPLUSARGS_%nq(%lw, %P, nullptr)"; }
@@ -1273,6 +1273,22 @@ public:
     bool cleanOut() const override { return true; }
     bool same(const AstNode* /*samep*/) const override { return true; }
 };
+class AstThisRef final : public AstNodeMath {
+    // Reference to 'this'.
+    // @astgen op1 := childDTypep : Optional[AstClassRefDType] // dtype of the node
+public:
+    explicit AstThisRef(FileLine* fl, AstClassRefDType* dtypep)
+        : ASTGEN_SUPER_ThisRef(fl) {
+        childDTypep(dtypep);
+    }
+    ASTGEN_MEMBERS_AstThisRef;
+    string emitC() override { return "this"; }
+    string emitVerilog() override { return "this"; }
+    bool same(const AstNode* /*samep*/) const override { return true; }
+    bool cleanOut() const override { return true; }
+    AstNodeDType* getChildDTypep() const override { return childDTypep(); }
+    virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); }
+};
 class AstUCFunc final : public AstNodeMath {
     // User's $c function
     // Perhaps this should be an AstNodeListop; but there's only one list math right now
@@ -1282,7 +1298,7 @@ public:
         : ASTGEN_SUPER_UCFunc(fl) {
         this->addExprsp(exprsp);
     }
-    ASTGEN_MEMBERS_UCFunc;
+    ASTGEN_MEMBERS_AstUCFunc;
     bool cleanOut() const override { return false; }
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -1302,7 +1318,7 @@ public:
         : ASTGEN_SUPER_Unbounded(fl) {
         dtypeSetSigned32();
     }
-    ASTGEN_MEMBERS_Unbounded;
+    ASTGEN_MEMBERS_AstUnbounded;
     string emitVerilog() override { return "$"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -1317,7 +1333,7 @@ public:
         this->searchp(searchp);
         this->outp(outp);
     }
-    ASTGEN_MEMBERS_ValuePlusArgs;
+    ASTGEN_MEMBERS_AstValuePlusArgs;
     string verilogKwd() const override { return "$value$plusargs"; }
     string emitVerilog() override { return "%f$value$plusargs(%l, %k%r)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -1338,7 +1354,7 @@ public:
         : ASTGEN_SUPER_BufIf1(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_BufIf1;
+    ASTGEN_MEMBERS_AstBufIf1;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstBufIf1(this->fileline(), lhsp, rhsp);
     }
@@ -1365,7 +1381,7 @@ class AstCastDynamic final : public AstNodeBiop {
 public:
     AstCastDynamic(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_CastDynamic(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_CastDynamic;
+    ASTGEN_MEMBERS_AstCastDynamic;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override {
         V3ERROR_NA;
     }
@@ -1394,7 +1410,7 @@ public:
         , m_ignoreCase{ignoreCase} {
         dtypeSetUInt32();
     }
-    ASTGEN_MEMBERS_CompareNN;
+    ASTGEN_MEMBERS_AstCompareNN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstCompareNN(this->fileline(), lhsp, rhsp, m_ignoreCase);
     }
@@ -1425,7 +1441,7 @@ public:
                                VSigning::UNSIGNED);
         }
     }
-    ASTGEN_MEMBERS_Concat;
+    ASTGEN_MEMBERS_AstConcat;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstConcat(this->fileline(), lhsp, rhsp);
     }
@@ -1448,7 +1464,7 @@ public:
         : ASTGEN_SUPER_ConcatN(fl, lhsp, rhsp) {
         dtypeSetString();
     }
-    ASTGEN_MEMBERS_ConcatN;
+    ASTGEN_MEMBERS_AstConcatN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstConcatN(this->fileline(), lhsp, rhsp);
     }
@@ -1471,7 +1487,7 @@ public:
         : ASTGEN_SUPER_Div(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Div;
+    ASTGEN_MEMBERS_AstDiv;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstDiv(this->fileline(), lhsp, rhsp);
     }
@@ -1493,7 +1509,7 @@ public:
         : ASTGEN_SUPER_DivD(fl, lhsp, rhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_DivD;
+    ASTGEN_MEMBERS_AstDivD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstDivD(this->fileline(), lhsp, rhsp);
     }
@@ -1517,7 +1533,7 @@ public:
         : ASTGEN_SUPER_DivS(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_DivS;
+    ASTGEN_MEMBERS_AstDivS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstDivS(this->fileline(), lhsp, rhsp);
     }
@@ -1541,7 +1557,7 @@ public:
         : ASTGEN_SUPER_EqWild(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_EqWild;
+    ASTGEN_MEMBERS_AstEqWild;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstEqWild(this->fileline(), lhsp, rhsp);
     }
@@ -1563,7 +1579,7 @@ class AstFGetS final : public AstNodeBiop {
 public:
     AstFGetS(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_FGetS(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_FGetS;
+    ASTGEN_MEMBERS_AstFGetS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstFGetS(this->fileline(), lhsp, rhsp);
     }
@@ -1588,7 +1604,7 @@ class AstFUngetC final : public AstNodeBiop {
 public:
     AstFUngetC(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_FUngetC(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_FUngetC;
+    ASTGEN_MEMBERS_AstFUngetC;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override {
         V3ERROR_NA;
     }
@@ -1617,7 +1633,7 @@ public:
         : ASTGEN_SUPER_GetcN(fl, lhsp, rhsp) {
         dtypeSetBitSized(8, VSigning::UNSIGNED);
     }
-    ASTGEN_MEMBERS_GetcN;
+    ASTGEN_MEMBERS_AstGetcN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGetcN(this->fileline(), lhsp, rhsp);
     }
@@ -1642,7 +1658,7 @@ public:
         : ASTGEN_SUPER_GetcRefN(fl, lhsp, rhsp) {
         dtypeSetBitSized(8, VSigning::UNSIGNED);
     }
-    ASTGEN_MEMBERS_GetcRefN;
+    ASTGEN_MEMBERS_AstGetcRefN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGetcRefN(this->fileline(), lhsp, rhsp);
     }
@@ -1664,7 +1680,7 @@ public:
         : ASTGEN_SUPER_Gt(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_Gt;
+    ASTGEN_MEMBERS_AstGt;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGt(this->fileline(), lhsp, rhsp);
     }
@@ -1686,7 +1702,7 @@ public:
         : ASTGEN_SUPER_GtD(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_GtD;
+    ASTGEN_MEMBERS_AstGtD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGtD(this->fileline(), lhsp, rhsp);
     }
@@ -1710,7 +1726,7 @@ public:
         : ASTGEN_SUPER_GtN(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_GtN;
+    ASTGEN_MEMBERS_AstGtN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGtN(this->fileline(), lhsp, rhsp);
     }
@@ -1734,7 +1750,7 @@ public:
         : ASTGEN_SUPER_GtS(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_GtS;
+    ASTGEN_MEMBERS_AstGtS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGtS(this->fileline(), lhsp, rhsp);
     }
@@ -1757,7 +1773,7 @@ public:
         : ASTGEN_SUPER_Gte(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_Gte;
+    ASTGEN_MEMBERS_AstGte;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGte(this->fileline(), lhsp, rhsp);
     }
@@ -1779,7 +1795,7 @@ public:
         : ASTGEN_SUPER_GteD(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_GteD;
+    ASTGEN_MEMBERS_AstGteD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGteD(this->fileline(), lhsp, rhsp);
     }
@@ -1803,7 +1819,7 @@ public:
         : ASTGEN_SUPER_GteN(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_GteN;
+    ASTGEN_MEMBERS_AstGteN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGteN(this->fileline(), lhsp, rhsp);
     }
@@ -1827,7 +1843,7 @@ public:
         : ASTGEN_SUPER_GteS(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_GteS;
+    ASTGEN_MEMBERS_AstGteS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstGteS(this->fileline(), lhsp, rhsp);
     }
@@ -1850,7 +1866,7 @@ public:
         : ASTGEN_SUPER_LogAnd(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LogAnd;
+    ASTGEN_MEMBERS_AstLogAnd;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLogAnd(this->fileline(), lhsp, rhsp);
     }
@@ -1873,7 +1889,7 @@ public:
         : ASTGEN_SUPER_LogIf(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LogIf;
+    ASTGEN_MEMBERS_AstLogIf;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLogIf(this->fileline(), lhsp, rhsp);
     }
@@ -1901,7 +1917,7 @@ public:
         : ASTGEN_SUPER_LogOr(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LogOr;
+    ASTGEN_MEMBERS_AstLogOr;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLogOr(this->fileline(), lhsp, rhsp);
     }
@@ -1932,7 +1948,7 @@ public:
         : ASTGEN_SUPER_Lt(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_Lt;
+    ASTGEN_MEMBERS_AstLt;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLt(this->fileline(), lhsp, rhsp);
     }
@@ -1954,7 +1970,7 @@ public:
         : ASTGEN_SUPER_LtD(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LtD;
+    ASTGEN_MEMBERS_AstLtD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLtD(this->fileline(), lhsp, rhsp);
     }
@@ -1978,7 +1994,7 @@ public:
         : ASTGEN_SUPER_LtN(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LtN;
+    ASTGEN_MEMBERS_AstLtN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLtN(this->fileline(), lhsp, rhsp);
     }
@@ -2002,7 +2018,7 @@ public:
         : ASTGEN_SUPER_LtS(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LtS;
+    ASTGEN_MEMBERS_AstLtS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLtS(this->fileline(), lhsp, rhsp);
     }
@@ -2025,7 +2041,7 @@ public:
         : ASTGEN_SUPER_Lte(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_Lte;
+    ASTGEN_MEMBERS_AstLte;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLte(this->fileline(), lhsp, rhsp);
     }
@@ -2047,7 +2063,7 @@ public:
         : ASTGEN_SUPER_LteD(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LteD;
+    ASTGEN_MEMBERS_AstLteD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLteD(this->fileline(), lhsp, rhsp);
     }
@@ -2071,7 +2087,7 @@ public:
         : ASTGEN_SUPER_LteN(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LteN;
+    ASTGEN_MEMBERS_AstLteN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLteN(this->fileline(), lhsp, rhsp);
     }
@@ -2095,7 +2111,7 @@ public:
         : ASTGEN_SUPER_LteS(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LteS;
+    ASTGEN_MEMBERS_AstLteS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLteS(this->fileline(), lhsp, rhsp);
     }
@@ -2118,7 +2134,7 @@ public:
         : ASTGEN_SUPER_ModDiv(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_ModDiv;
+    ASTGEN_MEMBERS_AstModDiv;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstModDiv(this->fileline(), lhsp, rhsp);
     }
@@ -2140,7 +2156,7 @@ public:
         : ASTGEN_SUPER_ModDivS(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_ModDivS;
+    ASTGEN_MEMBERS_AstModDivS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstModDivS(this->fileline(), lhsp, rhsp);
     }
@@ -2163,7 +2179,7 @@ public:
         : ASTGEN_SUPER_NeqWild(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_NeqWild;
+    ASTGEN_MEMBERS_AstNeqWild;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstNeqWild(this->fileline(), lhsp, rhsp);
     }
@@ -2185,7 +2201,7 @@ public:
         : ASTGEN_SUPER_Pow(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Pow;
+    ASTGEN_MEMBERS_AstPow;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstPow(this->fileline(), lhsp, rhsp);
     }
@@ -2208,7 +2224,7 @@ public:
         : ASTGEN_SUPER_PowD(fl, lhsp, rhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_PowD;
+    ASTGEN_MEMBERS_AstPowD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstPowD(this->fileline(), lhsp, rhsp);
     }
@@ -2231,7 +2247,7 @@ public:
         : ASTGEN_SUPER_PowSS(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_PowSS;
+    ASTGEN_MEMBERS_AstPowSS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstPowSS(this->fileline(), lhsp, rhsp);
     }
@@ -2255,7 +2271,7 @@ public:
         : ASTGEN_SUPER_PowSU(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_PowSU;
+    ASTGEN_MEMBERS_AstPowSU;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstPowSU(this->fileline(), lhsp, rhsp);
     }
@@ -2279,7 +2295,7 @@ public:
         : ASTGEN_SUPER_PowUS(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_PowUS;
+    ASTGEN_MEMBERS_AstPowUS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstPowUS(this->fileline(), lhsp, rhsp);
     }
@@ -2313,7 +2329,7 @@ public:
     }
     AstReplicate(FileLine* fl, AstNode* lhsp, uint32_t repCount)
         : AstReplicate(fl, lhsp, new AstConst(fl, repCount)) {}
-    ASTGEN_MEMBERS_Replicate;
+    ASTGEN_MEMBERS_AstReplicate;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstReplicate(this->fileline(), lhsp, rhsp);
     }
@@ -2338,7 +2354,7 @@ public:
     }
     AstReplicateN(FileLine* fl, AstNode* lhsp, uint32_t repCount)
         : AstReplicateN(fl, lhsp, new AstConst(fl, repCount)) {}
-    ASTGEN_MEMBERS_ReplicateN;
+    ASTGEN_MEMBERS_AstReplicateN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstReplicateN(this->fileline(), lhsp, rhsp);
     }
@@ -2361,7 +2377,7 @@ public:
         : ASTGEN_SUPER_ShiftL(fl, lhsp, rhsp) {
         if (setwidth) dtypeSetLogicSized(setwidth, VSigning::UNSIGNED);
     }
-    ASTGEN_MEMBERS_ShiftL;
+    ASTGEN_MEMBERS_AstShiftL;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstShiftL(this->fileline(), lhsp, rhsp);
     }
@@ -2385,7 +2401,7 @@ public:
         : ASTGEN_SUPER_ShiftR(fl, lhsp, rhsp) {
         if (setwidth) dtypeSetLogicSized(setwidth, VSigning::UNSIGNED);
     }
-    ASTGEN_MEMBERS_ShiftR;
+    ASTGEN_MEMBERS_AstShiftR;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstShiftR(this->fileline(), lhsp, rhsp);
     }
@@ -2413,7 +2429,7 @@ public:
         // Important that widthMin be correct, as opExtend requires it after V3Expand
         if (setwidth) dtypeSetLogicSized(setwidth, VSigning::SIGNED);
     }
-    ASTGEN_MEMBERS_ShiftRS;
+    ASTGEN_MEMBERS_AstShiftRS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstShiftRS(this->fileline(), lhsp, rhsp);
     }
@@ -2436,7 +2452,7 @@ public:
         : ASTGEN_SUPER_Sub(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Sub;
+    ASTGEN_MEMBERS_AstSub;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstSub(this->fileline(), lhsp, rhsp);
     }
@@ -2458,7 +2474,7 @@ public:
         : ASTGEN_SUPER_SubD(fl, lhsp, rhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_SubD;
+    ASTGEN_MEMBERS_AstSubD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstSubD(this->fileline(), lhsp, rhsp);
     }
@@ -2483,7 +2499,7 @@ public:
         : ASTGEN_SUPER_URandomRange(fl, lhsp, rhsp) {
         dtypeSetUInt32();  // Says IEEE
     }
-    ASTGEN_MEMBERS_URandomRange;
+    ASTGEN_MEMBERS_AstURandomRange;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstURandomRange(fileline(), lhsp, rhsp);
     }
@@ -2509,7 +2525,7 @@ public:
         : ASTGEN_SUPER_Eq(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_Eq;
+    ASTGEN_MEMBERS_AstEq;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstEq(this->fileline(), lhsp, rhsp);
     }
@@ -2533,7 +2549,7 @@ public:
         : ASTGEN_SUPER_EqCase(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_EqCase;
+    ASTGEN_MEMBERS_AstEqCase;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstEqCase(this->fileline(), lhsp, rhsp);
     }
@@ -2555,7 +2571,7 @@ public:
         : ASTGEN_SUPER_EqD(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_EqD;
+    ASTGEN_MEMBERS_AstEqD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstEqD(this->fileline(), lhsp, rhsp);
     }
@@ -2579,7 +2595,7 @@ public:
         : ASTGEN_SUPER_EqN(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_EqN;
+    ASTGEN_MEMBERS_AstEqN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstEqN(this->fileline(), lhsp, rhsp);
     }
@@ -2603,7 +2619,7 @@ public:
         : ASTGEN_SUPER_LogEq(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LogEq;
+    ASTGEN_MEMBERS_AstLogEq;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstLogEq(this->fileline(), lhsp, rhsp);
     }
@@ -2626,7 +2642,7 @@ public:
         : ASTGEN_SUPER_Neq(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_Neq;
+    ASTGEN_MEMBERS_AstNeq;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstNeq(this->fileline(), lhsp, rhsp);
     }
@@ -2648,7 +2664,7 @@ public:
         : ASTGEN_SUPER_NeqCase(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_NeqCase;
+    ASTGEN_MEMBERS_AstNeqCase;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstNeqCase(this->fileline(), lhsp, rhsp);
     }
@@ -2670,7 +2686,7 @@ public:
         : ASTGEN_SUPER_NeqD(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_NeqD;
+    ASTGEN_MEMBERS_AstNeqD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstNeqD(this->fileline(), lhsp, rhsp);
     }
@@ -2694,7 +2710,7 @@ public:
         : ASTGEN_SUPER_NeqN(fl, lhsp, rhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_NeqN;
+    ASTGEN_MEMBERS_AstNeqN;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstNeqN(this->fileline(), lhsp, rhsp);
     }
@@ -2720,7 +2736,7 @@ public:
         : ASTGEN_SUPER_Add(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Add;
+    ASTGEN_MEMBERS_AstAdd;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAdd(this->fileline(), lhsp, rhsp);
     }
@@ -2742,7 +2758,7 @@ public:
         : ASTGEN_SUPER_AddD(fl, lhsp, rhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_AddD;
+    ASTGEN_MEMBERS_AstAddD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAddD(this->fileline(), lhsp, rhsp);
     }
@@ -2766,7 +2782,7 @@ public:
         : ASTGEN_SUPER_And(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_And;
+    ASTGEN_MEMBERS_AstAnd;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAnd(this->fileline(), lhsp, rhsp);
     }
@@ -2788,7 +2804,7 @@ public:
         : ASTGEN_SUPER_Mul(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Mul;
+    ASTGEN_MEMBERS_AstMul;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstMul(this->fileline(), lhsp, rhsp);
     }
@@ -2811,7 +2827,7 @@ public:
         : ASTGEN_SUPER_MulD(fl, lhsp, rhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_MulD;
+    ASTGEN_MEMBERS_AstMulD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstMulD(this->fileline(), lhsp, rhsp);
     }
@@ -2835,7 +2851,7 @@ public:
         : ASTGEN_SUPER_MulS(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_MulS;
+    ASTGEN_MEMBERS_AstMulS;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstMulS(this->fileline(), lhsp, rhsp);
     }
@@ -2860,7 +2876,7 @@ public:
         : ASTGEN_SUPER_Or(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Or;
+    ASTGEN_MEMBERS_AstOr;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstOr(this->fileline(), lhsp, rhsp);
     }
@@ -2882,7 +2898,7 @@ public:
         : ASTGEN_SUPER_Xor(fl, lhsp, rhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Xor;
+    ASTGEN_MEMBERS_AstXor;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstXor(this->fileline(), lhsp, rhsp);
     }
@@ -2920,7 +2936,7 @@ public:
         : ASTGEN_SUPER_ArraySel(fl, fromp, new AstConst(fl, bit)) {
         init(fromp);
     }
-    ASTGEN_MEMBERS_ArraySel;
+    ASTGEN_MEMBERS_AstArraySel;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstArraySel(this->fileline(), lhsp, rhsp);
     }
@@ -2958,7 +2974,7 @@ public:
         : ASTGEN_SUPER_AssocSel(fl, fromp, bitp) {
         init(fromp);
     }
-    ASTGEN_MEMBERS_AssocSel;
+    ASTGEN_MEMBERS_AstAssocSel;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAssocSel(this->fileline(), lhsp, rhsp);
     }
@@ -2993,7 +3009,7 @@ public:
         : ASTGEN_SUPER_WildcardSel(fl, fromp, bitp) {
         init(fromp);
     }
-    ASTGEN_MEMBERS_WildcardSel;
+    ASTGEN_MEMBERS_AstWildcardSel;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstWildcardSel{this->fileline(), lhsp, rhsp};
     }
@@ -3019,7 +3035,7 @@ public:
         : ASTGEN_SUPER_WordSel(fl, fromp, bitp) {
         dtypeSetUInt32();  // Always used on WData arrays so returns edata size
     }
-    ASTGEN_MEMBERS_WordSel;
+    ASTGEN_MEMBERS_AstWordSel;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstWordSel(this->fileline(), lhsp, rhsp);
     }
@@ -3044,7 +3060,7 @@ class AstStreamL final : public AstNodeStream {
 public:
     AstStreamL(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_StreamL(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_StreamL;
+    ASTGEN_MEMBERS_AstStreamL;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstStreamL(this->fileline(), lhsp, rhsp);
     }
@@ -3065,7 +3081,7 @@ class AstStreamR final : public AstNodeStream {
 public:
     AstStreamR(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_StreamR(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_StreamR;
+    ASTGEN_MEMBERS_AstStreamR;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstStreamR(this->fileline(), lhsp, rhsp);
     }
@@ -3087,7 +3103,7 @@ class AstAtan2D final : public AstNodeSystemBiop {
 public:
     AstAtan2D(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_Atan2D(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_Atan2D;
+    ASTGEN_MEMBERS_AstAtan2D;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAtan2D(this->fileline(), lhsp, rhsp);
     }
@@ -3101,7 +3117,7 @@ class AstHypotD final : public AstNodeSystemBiop {
 public:
     AstHypotD(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_HypotD(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_HypotD;
+    ASTGEN_MEMBERS_AstHypotD;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstHypotD(this->fileline(), lhsp, rhsp);
     }
@@ -3123,7 +3139,7 @@ public:
         : ASTGEN_SUPER_CountBits(fl, exprp, ctrl1p, ctrl2p, ctrl2p->cloneTree(false)) {}
     AstCountBits(FileLine* fl, AstNode* exprp, AstNode* ctrl1p, AstNode* ctrl2p, AstNode* ctrl3p)
         : ASTGEN_SUPER_CountBits(fl, exprp, ctrl1p, ctrl2p, ctrl3p) {}
-    ASTGEN_MEMBERS_CountBits;
+    ASTGEN_MEMBERS_AstCountBits;
     void numberOperate(V3Number& out, const V3Number& expr, const V3Number& ctrl1,
                        const V3Number& ctrl2, const V3Number& ctrl3) override {
         out.opCountBits(expr, ctrl1, ctrl2, ctrl3);
@@ -3151,7 +3167,7 @@ public:
         , m_timeunit{timeunit} {
         dtypeSetUInt64();
     }
-    ASTGEN_MEMBERS_Time;
+    ASTGEN_MEMBERS_AstTime;
     string emitVerilog() override { return "%f$time"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -3171,7 +3187,7 @@ public:
         , m_timeunit{timeunit} {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_TimeD;
+    ASTGEN_MEMBERS_AstTimeD;
     string emitVerilog() override { return "%f$realtime"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -3194,7 +3210,7 @@ class AstPostAdd final : public AstNodeTriop {
 public:
     AstPostAdd(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp)
         : ASTGEN_SUPER_PostAdd(fl, lhsp, rhsp, thsp) {}
-    ASTGEN_MEMBERS_PostAdd;
+    ASTGEN_MEMBERS_AstPostAdd;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
                        const V3Number& ths) override {
         V3ERROR_NA;  // Need to modify lhs
@@ -3219,7 +3235,7 @@ class AstPostSub final : public AstNodeTriop {
 public:
     AstPostSub(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp)
         : ASTGEN_SUPER_PostSub(fl, lhsp, rhsp, thsp) {}
-    ASTGEN_MEMBERS_PostSub;
+    ASTGEN_MEMBERS_AstPostSub;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
                        const V3Number& ths) override {
         V3ERROR_NA;  // Need to modify lhs
@@ -3244,7 +3260,7 @@ class AstPreAdd final : public AstNodeTriop {
 public:
     AstPreAdd(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp)
         : ASTGEN_SUPER_PreAdd(fl, lhsp, rhsp, thsp) {}
-    ASTGEN_MEMBERS_PreAdd;
+    ASTGEN_MEMBERS_AstPreAdd;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
                        const V3Number& ths) override {
         V3ERROR_NA;  // Need to modify lhs
@@ -3269,7 +3285,7 @@ class AstPreSub final : public AstNodeTriop {
 public:
     AstPreSub(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp)
         : ASTGEN_SUPER_PreSub(fl, lhsp, rhsp, thsp) {}
-    ASTGEN_MEMBERS_PreSub;
+    ASTGEN_MEMBERS_AstPreSub;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
                        const V3Number& ths) override {
         V3ERROR_NA;  // Need to modify lhs
@@ -3292,7 +3308,7 @@ public:
         : ASTGEN_SUPER_PutcN(fl, lhsp, rhsp, ths) {
         dtypeSetString();
     }
-    ASTGEN_MEMBERS_PutcN;
+    ASTGEN_MEMBERS_AstPutcN;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
                        const V3Number& ths) override {
         out.opPutcN(lhs, rhs, ths);
@@ -3332,7 +3348,7 @@ public:
         , m_declElWidth{1} {
         dtypeSetLogicSized(bitwidth, VSigning::UNSIGNED);
     }
-    ASTGEN_MEMBERS_Sel;
+    ASTGEN_MEMBERS_AstSel;
     void dump(std::ostream& str) const override;
     void numberOperate(V3Number& out, const V3Number& from, const V3Number& bit,
                        const V3Number& width) override {
@@ -3373,7 +3389,7 @@ public:
         : ASTGEN_SUPER_SliceSel(fl, fromp, new AstConst(fl, declRange.lo()),
                                 new AstConst(fl, declRange.elements()))
         , m_declRange{declRange} {}
-    ASTGEN_MEMBERS_SliceSel;
+    ASTGEN_MEMBERS_AstSliceSel;
     void dump(std::ostream& str) const override;
     void numberOperate(V3Number& out, const V3Number& from, const V3Number& lo,
                        const V3Number& width) override {
@@ -3402,7 +3418,7 @@ public:
         : ASTGEN_SUPER_SubstrN(fl, lhsp, rhsp, ths) {
         dtypeSetString();
     }
-    ASTGEN_MEMBERS_SubstrN;
+    ASTGEN_MEMBERS_AstSubstrN;
     void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
                        const V3Number& ths) override {
         out.opSubstrN(lhs, rhs, ths);
@@ -3428,7 +3444,7 @@ class AstCond final : public AstNodeCond {
 public:
     AstCond(FileLine* fl, AstNode* condp, AstNode* thenp, AstNode* elsep)
         : ASTGEN_SUPER_Cond(fl, condp, thenp, elsep) {}
-    ASTGEN_MEMBERS_Cond;
+    ASTGEN_MEMBERS_AstCond;
     AstNode* cloneType(AstNode* condp, AstNode* thenp, AstNode* elsep) override {
         return new AstCond(this->fileline(), condp, thenp, elsep);
     }
@@ -3440,7 +3456,7 @@ class AstCondBound final : public AstNodeCond {
 public:
     AstCondBound(FileLine* fl, AstNode* condp, AstNode* thenp, AstNode* elsep)
         : ASTGEN_SUPER_CondBound(fl, condp, thenp, elsep) {}
-    ASTGEN_MEMBERS_CondBound;
+    ASTGEN_MEMBERS_AstCondBound;
     AstNode* cloneType(AstNode* condp, AstNode* thenp, AstNode* elsep) override {
         return new AstCondBound(this->fileline(), condp, thenp, elsep);
     }
@@ -3460,7 +3476,7 @@ public:
         , m_fmt{fmt} {
         fmt == ATOREAL ? dtypeSetDouble() : dtypeSetSigned32();
     }
-    ASTGEN_MEMBERS_AtoN;
+    ASTGEN_MEMBERS_AstAtoN;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAtoN(lhs, m_fmt); }
     string name() const override {
         switch (m_fmt) {
@@ -3494,7 +3510,7 @@ public:
         : ASTGEN_SUPER_BitsToRealD(fl, lhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_BitsToRealD;
+    ASTGEN_MEMBERS_AstBitsToRealD;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opBitsToRealD(lhs); }
     string emitVerilog() override { return "%f$bitstoreal(%l)"; }
     string emitC() override { return "VL_CVT_D_Q(%li)"; }
@@ -3522,7 +3538,7 @@ public:
         dtypeFrom(typeFromp);
         m_size = width();
     }
-    ASTGEN_MEMBERS_CCast;
+    ASTGEN_MEMBERS_AstCCast;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAssign(lhs); }
     string emitVerilog() override { return "%f$_CAST(%l)"; }
     string emitC() override { return "VL_CAST_%nq%lq(%nw,%lw, %P, %li)"; }
@@ -3542,7 +3558,7 @@ public:
         : ASTGEN_SUPER_CLog2(fl, lhsp) {
         dtypeSetSigned32();
     }
-    ASTGEN_MEMBERS_CLog2;
+    ASTGEN_MEMBERS_AstCLog2;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opCLog2(lhs); }
     string emitVerilog() override { return "%f$clog2(%l)"; }
     string emitC() override { return "VL_CLOG2_%lq(%lW, %P, %li)"; }
@@ -3556,7 +3572,7 @@ class AstCountOnes final : public AstNodeUniop {
 public:
     AstCountOnes(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_CountOnes(fl, lhsp) {}
-    ASTGEN_MEMBERS_CountOnes;
+    ASTGEN_MEMBERS_AstCountOnes;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opCountOnes(lhs); }
     string emitVerilog() override { return "%f$countones(%l)"; }
     string emitC() override { return "VL_COUNTONES_%lq(%lW, %P, %li)"; }
@@ -3572,7 +3588,7 @@ public:
         : ASTGEN_SUPER_CvtPackString(fl, lhsp) {
         dtypeSetString();
     }
-    ASTGEN_MEMBERS_CvtPackString;
+    ASTGEN_MEMBERS_AstCvtPackString;
     void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
     string emitVerilog() override { return "%f$_CAST(%l)"; }
     string emitC() override { return "VL_CVT_PACK_STR_N%lq(%lW, %li)"; }
@@ -3590,7 +3606,7 @@ public:
         : ASTGEN_SUPER_Extend(fl, lhsp) {
         dtypeSetLogicSized(width, VSigning::UNSIGNED);
     }
-    ASTGEN_MEMBERS_Extend;
+    ASTGEN_MEMBERS_AstExtend;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAssign(lhs); }
     string emitVerilog() override { return "%l"; }
     string emitC() override { return "VL_EXTEND_%nq%lq(%nw,%lw, %P, %li)"; }
@@ -3611,7 +3627,7 @@ public:
         : ASTGEN_SUPER_ExtendS(fl, lhsp) {
         dtypeSetLogicSized(width, VSigning::UNSIGNED);
     }
-    ASTGEN_MEMBERS_ExtendS;
+    ASTGEN_MEMBERS_AstExtendS;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.opExtendS(lhs, lhsp()->widthMinV());
     }
@@ -3629,7 +3645,7 @@ class AstFEof final : public AstNodeUniop {
 public:
     AstFEof(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_FEof(fl, lhsp) {}
-    ASTGEN_MEMBERS_FEof;
+    ASTGEN_MEMBERS_AstFEof;
     void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
     string emitVerilog() override { return "%f$feof(%l)"; }
     string emitC() override { return "(%li ? feof(VL_CVT_I_FP(%li)) : true)"; }
@@ -3644,7 +3660,7 @@ class AstFGetC final : public AstNodeUniop {
 public:
     AstFGetC(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_FGetC(fl, lhsp) {}
-    ASTGEN_MEMBERS_FGetC;
+    ASTGEN_MEMBERS_AstFGetC;
     void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
     string emitVerilog() override { return "%f$fgetc(%l)"; }
     // Non-existent filehandle returns EOF
@@ -3663,7 +3679,7 @@ public:
         : ASTGEN_SUPER_ISToRD(fl, lhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_ISToRD;
+    ASTGEN_MEMBERS_AstISToRD;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opISToRD(lhs); }
     string emitVerilog() override { return "%f$itor($signed(%l))"; }
     string emitC() override { return "VL_ISTOR_D_%lq(%lw, %li)"; }
@@ -3680,7 +3696,7 @@ public:
         : ASTGEN_SUPER_IToRD(fl, lhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_IToRD;
+    ASTGEN_MEMBERS_AstIToRD;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opIToRD(lhs); }
     string emitVerilog() override { return "%f$itor(%l)"; }
     string emitC() override { return "VL_ITOR_D_%lq(%lw, %li)"; }
@@ -3696,7 +3712,7 @@ public:
         : ASTGEN_SUPER_IsUnbounded(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_IsUnbounded;
+    ASTGEN_MEMBERS_AstIsUnbounded;
     void numberOperate(V3Number& out, const V3Number&) override {
         // Any constant isn't unbounded
         out.setZero();
@@ -3714,7 +3730,7 @@ public:
         : ASTGEN_SUPER_IsUnknown(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_IsUnknown;
+    ASTGEN_MEMBERS_AstIsUnknown;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opIsUnknown(lhs); }
     string emitVerilog() override { return "%f$isunknown(%l)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -3729,7 +3745,7 @@ public:
         : ASTGEN_SUPER_LenN(fl, lhsp) {
         dtypeSetSigned32();
     }
-    ASTGEN_MEMBERS_LenN;
+    ASTGEN_MEMBERS_AstLenN;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opLenN(lhs); }
     string emitVerilog() override { return "%f(%l)"; }
     string emitC() override { return "VL_LEN_IN(%li)"; }
@@ -3743,7 +3759,7 @@ public:
         : ASTGEN_SUPER_LogNot(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_LogNot;
+    ASTGEN_MEMBERS_AstLogNot;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opLogNot(lhs); }
     string emitVerilog() override { return "%f(! %l)"; }
     string emitC() override { return "VL_LOGNOT_%nq%lq(%nw,%lw, %P, %li)"; }
@@ -3758,7 +3774,7 @@ public:
         : ASTGEN_SUPER_Negate(fl, lhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Negate;
+    ASTGEN_MEMBERS_AstNegate;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opNegate(lhs); }
     string emitVerilog() override { return "%f(- %l)"; }
     string emitC() override { return "VL_NEGATE_%lq(%lW, %P, %li)"; }
@@ -3773,7 +3789,7 @@ public:
         : ASTGEN_SUPER_NegateD(fl, lhsp) {
         dtypeSetDouble();
     }
-    ASTGEN_MEMBERS_NegateD;
+    ASTGEN_MEMBERS_AstNegateD;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opNegateD(lhs); }
     string emitVerilog() override { return "%f(- %l)"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -3790,7 +3806,7 @@ public:
         : ASTGEN_SUPER_Not(fl, lhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Not;
+    ASTGEN_MEMBERS_AstNot;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opNot(lhs); }
     string emitVerilog() override { return "%f(~ %l)"; }
     string emitC() override { return "VL_NOT_%lq(%lW, %P, %li)"; }
@@ -3807,7 +3823,7 @@ public:
         : ASTGEN_SUPER_NullCheck(fl, lhsp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_NullCheck;
+    ASTGEN_MEMBERS_AstNullCheck;
     void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
     int instrCount() const override { return 1; }  // Rarely executes
     string emitVerilog() override { return "%l"; }
@@ -3825,7 +3841,7 @@ public:
         : ASTGEN_SUPER_OneHot(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_OneHot;
+    ASTGEN_MEMBERS_AstOneHot;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opOneHot(lhs); }
     string emitVerilog() override { return "%f$onehot(%l)"; }
     string emitC() override { return "VL_ONEHOT_%lq(%lW, %P, %li)"; }
@@ -3841,7 +3857,7 @@ public:
         : ASTGEN_SUPER_OneHot0(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_OneHot0;
+    ASTGEN_MEMBERS_AstOneHot0;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opOneHot0(lhs); }
     string emitVerilog() override { return "%f$onehot0(%l)"; }
     string emitC() override { return "VL_ONEHOT0_%lq(%lW, %P, %li)"; }
@@ -3857,7 +3873,7 @@ public:
         : ASTGEN_SUPER_RToIRoundS(fl, lhsp) {
         dtypeSetSigned32();
     }
-    ASTGEN_MEMBERS_RToIRoundS;
+    ASTGEN_MEMBERS_AstRToIRoundS;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRToIRoundS(lhs); }
     string emitVerilog() override { return "%f$rtoi_rounded(%l)"; }
     string emitC() override {
@@ -3875,7 +3891,7 @@ public:
         : ASTGEN_SUPER_RToIS(fl, lhsp) {
         dtypeSetSigned32();
     }
-    ASTGEN_MEMBERS_RToIS;
+    ASTGEN_MEMBERS_AstRToIS;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRToIS(lhs); }
     string emitVerilog() override { return "%f$rtoi(%l)"; }
     string emitC() override { return "VL_RTOI_I_D(%li)"; }
@@ -3890,7 +3906,7 @@ public:
         : ASTGEN_SUPER_RealToBits(fl, lhsp) {
         dtypeSetUInt64();
     }
-    ASTGEN_MEMBERS_RealToBits;
+    ASTGEN_MEMBERS_AstRealToBits;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRealToBits(lhs); }
     string emitVerilog() override { return "%f$realtobits(%l)"; }
     string emitC() override { return "VL_CVT_Q_D(%li)"; }
@@ -3905,7 +3921,7 @@ public:
         : ASTGEN_SUPER_RedAnd(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_RedAnd;
+    ASTGEN_MEMBERS_AstRedAnd;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRedAnd(lhs); }
     string emitVerilog() override { return "%f(& %l)"; }
     string emitC() override { return "VL_REDAND_%nq%lq(%lw, %P, %li)"; }
@@ -3919,7 +3935,7 @@ public:
         : ASTGEN_SUPER_RedOr(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_RedOr;
+    ASTGEN_MEMBERS_AstRedOr;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRedOr(lhs); }
     string emitVerilog() override { return "%f(| %l)"; }
     string emitC() override { return "VL_REDOR_%lq(%lW, %P, %li)"; }
@@ -3933,7 +3949,7 @@ public:
         : ASTGEN_SUPER_RedXor(fl, lhsp) {
         dtypeSetBit();
     }
-    ASTGEN_MEMBERS_RedXor;
+    ASTGEN_MEMBERS_AstRedXor;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRedXor(lhs); }
     string emitVerilog() override { return "%f(^ %l)"; }
     string emitC() override { return "VL_REDXOR_%lq(%lW, %P, %li)"; }
@@ -3953,7 +3969,7 @@ public:
         UASSERT_OBJ(!v3Global.assertDTypesResolved(), this,
                     "not coded to create after dtypes resolved");
     }
-    ASTGEN_MEMBERS_Signed;
+    ASTGEN_MEMBERS_AstSigned;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.opAssign(lhs);
         out.isSigned(false);
@@ -3971,7 +3987,7 @@ class AstTimeImport final : public AstNodeUniop {
 public:
     AstTimeImport(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_TimeImport(fl, lhsp) {}
-    ASTGEN_MEMBERS_TimeImport;
+    ASTGEN_MEMBERS_AstTimeImport;
     void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
     string emitVerilog() override { return "%l"; }
     string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -3989,7 +4005,7 @@ public:
         : ASTGEN_SUPER_ToLowerN(fl, lhsp) {
         dtypeSetString();
     }
-    ASTGEN_MEMBERS_ToLowerN;
+    ASTGEN_MEMBERS_AstToLowerN;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opToLowerN(lhs); }
     string emitVerilog() override { return "%l.tolower()"; }
     string emitC() override { return "VL_TOLOWER_NN(%li)"; }
@@ -4004,7 +4020,7 @@ public:
         : ASTGEN_SUPER_ToUpperN(fl, lhsp) {
         dtypeSetString();
     }
-    ASTGEN_MEMBERS_ToUpperN;
+    ASTGEN_MEMBERS_AstToUpperN;
     void numberOperate(V3Number& out, const V3Number& lhs) override { out.opToUpperN(lhs); }
     string emitVerilog() override { return "%l.toupper()"; }
     string emitC() override { return "VL_TOUPPER_NN(%li)"; }
@@ -4020,7 +4036,7 @@ public:
         UASSERT_OBJ(!v3Global.assertDTypesResolved(), this,
                     "not coded to create after dtypes resolved");
     }
-    ASTGEN_MEMBERS_Unsigned;
+    ASTGEN_MEMBERS_AstUnsigned;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.opAssign(lhs);
         out.isSigned(false);
@@ -4038,7 +4054,7 @@ class AstAcosD final : public AstNodeSystemUniop {
 public:
     AstAcosD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_AcosD(fl, lhsp) {}
-    ASTGEN_MEMBERS_AcosD;
+    ASTGEN_MEMBERS_AstAcosD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::acos(lhs.toDouble()));
     }
@@ -4049,7 +4065,7 @@ class AstAcoshD final : public AstNodeSystemUniop {
 public:
     AstAcoshD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_AcoshD(fl, lhsp) {}
-    ASTGEN_MEMBERS_AcoshD;
+    ASTGEN_MEMBERS_AstAcoshD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::acosh(lhs.toDouble()));
     }
@@ -4060,7 +4076,7 @@ class AstAsinD final : public AstNodeSystemUniop {
 public:
     AstAsinD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_AsinD(fl, lhsp) {}
-    ASTGEN_MEMBERS_AsinD;
+    ASTGEN_MEMBERS_AstAsinD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::asin(lhs.toDouble()));
     }
@@ -4071,7 +4087,7 @@ class AstAsinhD final : public AstNodeSystemUniop {
 public:
     AstAsinhD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_AsinhD(fl, lhsp) {}
-    ASTGEN_MEMBERS_AsinhD;
+    ASTGEN_MEMBERS_AstAsinhD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::asinh(lhs.toDouble()));
     }
@@ -4082,7 +4098,7 @@ class AstAtanD final : public AstNodeSystemUniop {
 public:
     AstAtanD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_AtanD(fl, lhsp) {}
-    ASTGEN_MEMBERS_AtanD;
+    ASTGEN_MEMBERS_AstAtanD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::atan(lhs.toDouble()));
     }
@@ -4093,7 +4109,7 @@ class AstAtanhD final : public AstNodeSystemUniop {
 public:
     AstAtanhD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_AtanhD(fl, lhsp) {}
-    ASTGEN_MEMBERS_AtanhD;
+    ASTGEN_MEMBERS_AstAtanhD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::atanh(lhs.toDouble()));
     }
@@ -4104,7 +4120,7 @@ class AstCeilD final : public AstNodeSystemUniop {
 public:
     AstCeilD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_CeilD(fl, lhsp) {}
-    ASTGEN_MEMBERS_CeilD;
+    ASTGEN_MEMBERS_AstCeilD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::ceil(lhs.toDouble()));
     }
@@ -4115,7 +4131,7 @@ class AstCosD final : public AstNodeSystemUniop {
 public:
     AstCosD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_CosD(fl, lhsp) {}
-    ASTGEN_MEMBERS_CosD;
+    ASTGEN_MEMBERS_AstCosD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::cos(lhs.toDouble()));
     }
@@ -4126,7 +4142,7 @@ class AstCoshD final : public AstNodeSystemUniop {
 public:
     AstCoshD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_CoshD(fl, lhsp) {}
-    ASTGEN_MEMBERS_CoshD;
+    ASTGEN_MEMBERS_AstCoshD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::cosh(lhs.toDouble()));
     }
@@ -4137,7 +4153,7 @@ class AstExpD final : public AstNodeSystemUniop {
 public:
     AstExpD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_ExpD(fl, lhsp) {}
-    ASTGEN_MEMBERS_ExpD;
+    ASTGEN_MEMBERS_AstExpD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::exp(lhs.toDouble()));
     }
@@ -4148,7 +4164,7 @@ class AstFloorD final : public AstNodeSystemUniop {
 public:
     AstFloorD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_FloorD(fl, lhsp) {}
-    ASTGEN_MEMBERS_FloorD;
+    ASTGEN_MEMBERS_AstFloorD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::floor(lhs.toDouble()));
     }
@@ -4159,7 +4175,7 @@ class AstLog10D final : public AstNodeSystemUniop {
 public:
     AstLog10D(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_Log10D(fl, lhsp) {}
-    ASTGEN_MEMBERS_Log10D;
+    ASTGEN_MEMBERS_AstLog10D;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::log10(lhs.toDouble()));
     }
@@ -4170,7 +4186,7 @@ class AstLogD final : public AstNodeSystemUniop {
 public:
     AstLogD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_LogD(fl, lhsp) {}
-    ASTGEN_MEMBERS_LogD;
+    ASTGEN_MEMBERS_AstLogD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::log(lhs.toDouble()));
     }
@@ -4181,7 +4197,7 @@ class AstSinD final : public AstNodeSystemUniop {
 public:
     AstSinD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_SinD(fl, lhsp) {}
-    ASTGEN_MEMBERS_SinD;
+    ASTGEN_MEMBERS_AstSinD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::sin(lhs.toDouble()));
     }
@@ -4192,7 +4208,7 @@ class AstSinhD final : public AstNodeSystemUniop {
 public:
     AstSinhD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_SinhD(fl, lhsp) {}
-    ASTGEN_MEMBERS_SinhD;
+    ASTGEN_MEMBERS_AstSinhD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::sinh(lhs.toDouble()));
     }
@@ -4203,7 +4219,7 @@ class AstSqrtD final : public AstNodeSystemUniop {
 public:
     AstSqrtD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_SqrtD(fl, lhsp) {}
-    ASTGEN_MEMBERS_SqrtD;
+    ASTGEN_MEMBERS_AstSqrtD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::sqrt(lhs.toDouble()));
     }
@@ -4214,7 +4230,7 @@ class AstTanD final : public AstNodeSystemUniop {
 public:
     AstTanD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_TanD(fl, lhsp) {}
-    ASTGEN_MEMBERS_TanD;
+    ASTGEN_MEMBERS_AstTanD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::tan(lhs.toDouble()));
     }
@@ -4225,7 +4241,7 @@ class AstTanhD final : public AstNodeSystemUniop {
 public:
     AstTanhD(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_TanhD(fl, lhsp) {}
-    ASTGEN_MEMBERS_TanhD;
+    ASTGEN_MEMBERS_AstTanhD;
     void numberOperate(V3Number& out, const V3Number& lhs) override {
         out.setDouble(std::tanh(lhs.toDouble()));
     }
@@ -4244,14 +4260,12 @@ public:
     inline AstVarRef(FileLine* fl, AstVar* varp, const VAccess& access);
     // This form only allowed post-link (see above)
     inline AstVarRef(FileLine* fl, AstVarScope* varscp, const VAccess& access);
-    ASTGEN_MEMBERS_VarRef;
+    ASTGEN_MEMBERS_AstVarRef;
     void dump(std::ostream& str) const override;
     bool same(const AstNode* samep) const override;
     inline bool same(const AstVarRef* samep) const;
     inline bool sameNoLvalue(AstVarRef* samep) const;
-    int instrCount() const override {
-        return widthInstrs() * (access().isReadOrRW() ? INSTR_COUNT_LD : 1);
-    }
+    int instrCount() const override;
     string emitVerilog() override { V3ERROR_NA_RETURN(""); }
     string emitC() override { V3ERROR_NA_RETURN(""); }
     bool cleanOut() const override { return true; }
@@ -4267,7 +4281,7 @@ public:
         : ASTGEN_SUPER_VarXRef(fl, name, nullptr, access)
         , m_dotted{dotted} {}
     inline AstVarXRef(FileLine* fl, AstVar* varp, const string& dotted, const VAccess& access);
-    ASTGEN_MEMBERS_VarXRef;
+    ASTGEN_MEMBERS_AstVarXRef;
     void dump(std::ostream& str) const override;
     string dotted() const { return m_dotted; }
     void dotted(const string& dotted) { m_dotted = dotted; }
diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h
index c386496ed..a0b3e991f 100644
--- a/src/V3AstNodeOther.h
+++ b/src/V3AstNodeOther.h
@@ -46,7 +46,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeBlock;
+    ASTGEN_MEMBERS_AstNodeBlock;
     void dump(std::ostream& str) const override;
     string name() const override { return m_name; }  // * = Block name
     void name(const string& name) override { m_name = name; }
@@ -116,7 +116,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeFTask;
+    ASTGEN_MEMBERS_AstNodeFTask;
     void dump(std::ostream& str = std::cout) const override;
     string name() const override { return m_name; }  // * = Var name
     bool maybePointedTo() const override { return true; }
@@ -184,7 +184,7 @@ public:
     AstNodeFile(VNType t, FileLine* fl, const string& name)
         : AstNode{t, fl}
         , m_name{name} {}
-    ASTGEN_MEMBERS_NodeFile;
+    ASTGEN_MEMBERS_AstNodeFile;
     void dump(std::ostream& str) const override;
     string name() const override { return m_name; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -228,7 +228,7 @@ protected:
         , m_recursiveClone{false} {}
 
 public:
-    ASTGEN_MEMBERS_NodeModule;
+    ASTGEN_MEMBERS_AstNodeModule;
     void dump(std::ostream& str) const override;
     bool maybePointedTo() const override { return true; }
     string name() const override { return m_name; }
@@ -236,13 +236,13 @@ public:
     // ACCESSORS
     void name(const string& name) override { m_name = name; }
     string origName() const override { return m_origName; }
-    string someInstanceName() const { return m_someInstanceName; }
+    string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; }
     void someInstanceName(const string& name) { m_someInstanceName = name; }
     bool inLibrary() const { return m_inLibrary; }
     void inLibrary(bool flag) { m_inLibrary = flag; }
     void level(int level) { m_level = level; }
-    int level() const { return m_level; }
-    bool isTop() const { return level() == 1; }
+    int level() const VL_MT_SAFE { return m_level; }
+    bool isTop() const VL_MT_SAFE { return level() == 1; }
     void modPublic(bool flag) { m_modPublic = flag; }
     bool modPublic() const { return m_modPublic; }
     void modTrace(bool flag) { m_modTrace = flag; }
@@ -279,13 +279,14 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodePreSel;
+    ASTGEN_MEMBERS_AstNodePreSel;
     // METHODS
     bool same(const AstNode*) const override { return true; }
 };
 class AstNodeProcedure VL_NOT_FINAL : public AstNode {
     // IEEE procedure: initial, final, always
     // @astgen op2 := stmtsp : List[AstNode] // Note: op1 is used in some sub-types only
+    bool m_suspendable = false;  // Is suspendable by a Delay, EventControl, etc.
 protected:
     AstNodeProcedure(VNType t, FileLine* fl, AstNode* stmtsp)
         : AstNode{t, fl} {
@@ -293,10 +294,12 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeProcedure;
+    ASTGEN_MEMBERS_AstNodeProcedure;
     // METHODS
     void dump(std::ostream& str) const override;
     bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); }
+    bool isSuspendable() const { return m_suspendable; }
+    void setSuspendable() { m_suspendable = true; }
 };
 class AstNodeRange VL_NOT_FINAL : public AstNode {
     // A range, sized or unsized
@@ -305,7 +308,7 @@ protected:
         : AstNode{t, fl} {}
 
 public:
-    ASTGEN_MEMBERS_NodeRange;
+    ASTGEN_MEMBERS_AstNodeRange;
     void dump(std::ostream& str) const override;
 };
 class AstNodeStmt VL_NOT_FINAL : public AstNode {
@@ -317,7 +320,7 @@ protected:
         , m_statement{statement} {}
 
 public:
-    ASTGEN_MEMBERS_NodeStmt;
+    ASTGEN_MEMBERS_AstNodeStmt;
     // METHODS
     bool isStatement() const { return m_statement; }  // Really a statement
     void statement(bool flag) { m_statement = flag; }
@@ -343,7 +346,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeAssign;
+    ASTGEN_MEMBERS_AstNodeAssign;
     // Clone single node, just get same type back.
     virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) = 0;
     bool hasDType() const override { return true; }
@@ -351,6 +354,7 @@ public:
     int instrCount() const override { return widthInstrs(); }
     bool same(const AstNode*) const override { return true; }
     string verilogKwd() const override { return "="; }
+    bool isTimingControl() const override { return timingControlp(); }
     virtual bool brokeLhsMustBeLvalue() const = 0;
 };
 class AstNodeCCall VL_NOT_FINAL : public AstNodeStmt {
@@ -369,7 +373,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeCCall;
+    ASTGEN_MEMBERS_AstNodeCCall;
     void dump(std::ostream& str = std::cout) const override;
     void cloneRelink() override;
     const char* broken() const override;
@@ -399,7 +403,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeCase;
+    ASTGEN_MEMBERS_AstNodeCase;
     int instrCount() const override { return INSTR_COUNT_BRANCH; }
 };
 class AstNodeCoverOrAssert VL_NOT_FINAL : public AstNodeStmt {
@@ -420,7 +424,7 @@ public:
         this->propp(propp);
         this->addPasssp(passsp);
     }
-    ASTGEN_MEMBERS_NodeCoverOrAssert;
+    ASTGEN_MEMBERS_AstNodeCoverOrAssert;
     string name() const override { return m_name; }  // * = Var name
     bool same(const AstNode* samep) const override { return samep->name() == name(); }
     void name(const string& name) override { m_name = name; }
@@ -455,7 +459,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeFTaskRef;
+    ASTGEN_MEMBERS_AstNodeFTaskRef;
     const char* broken() const override;
     void cloneRelink() override;
     void dump(std::ostream& str = std::cout) const override;
@@ -489,7 +493,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeFor;
+    ASTGEN_MEMBERS_AstNodeFor;
     bool isGateOptimizable() const override { return false; }
     int instrCount() const override { return INSTR_COUNT_BRANCH; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -500,7 +504,7 @@ class AstNodeIf VL_NOT_FINAL : public AstNodeStmt {
     // @astgen op3 := elsesp : List[AstNode]
 private:
     VBranchPred m_branchPred;  // Branch prediction as taken/untaken?
-    bool m_isBoundsCheck;  // True if this if node was inserted for array bounds checking
+    bool m_isBoundsCheck;  // True if this if node is for assertion/bounds checking
 protected:
     AstNodeIf(VNType t, FileLine* fl, AstNode* condp, AstNode* thensp, AstNode* elsesp)
         : AstNodeStmt{t, fl} {
@@ -511,7 +515,7 @@ protected:
     }
 
 public:
-    ASTGEN_MEMBERS_NodeIf;
+    ASTGEN_MEMBERS_AstNodeIf;
     bool isGateOptimizable() const override { return false; }
     bool isGateDedupable() const override { return true; }
     int instrCount() const override { return INSTR_COUNT_BRANCH; }
@@ -541,7 +545,7 @@ public:
         this->lsbp(lsbp);
         this->msbp(msbp);
     }
-    ASTGEN_MEMBERS_NodeReadWriteMem;
+    ASTGEN_MEMBERS_AstNodeReadWriteMem;
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
     bool isPure() const override { return false; }
@@ -564,13 +568,14 @@ protected:
         , m_text{text} {}
 
 public:
-    ASTGEN_MEMBERS_NodeText;
+    ASTGEN_MEMBERS_AstNodeText;
     void dump(std::ostream& str = std::cout) const override;
     bool same(const AstNode* samep) const override {
         const AstNodeText* asamep = static_cast(samep);
         return text() == asamep->text();
     }
-    const string& text() const { return m_text; }
+    const string& text() const VL_MT_SAFE { return m_text; }
+    void text(const string& value) { m_text = value; }
 };
 class AstNodeSimpleText VL_NOT_FINAL : public AstNodeText {
 private:
@@ -579,7 +584,7 @@ public:
     AstNodeSimpleText(VNType t, FileLine* fl, const string& textp, bool tracking = false)
         : AstNodeText(t, fl, textp)
         , m_tracking(tracking) {}
-    ASTGEN_MEMBERS_NodeSimpleText;
+    ASTGEN_MEMBERS_AstNodeSimpleText;
     void tracking(bool flag) { m_tracking = flag; }
     bool tracking() const { return m_tracking; }
 };
@@ -603,7 +608,7 @@ public:
         , m_sensesp{sensesp} {
         UASSERT(sensesp, "Sensesp required arg");
     }
-    ASTGEN_MEMBERS_Active;
+    ASTGEN_MEMBERS_AstActive;
     void dump(std::ostream& str = std::cout) const override;
     string name() const override { return m_name; }
     const char* broken() const override;
@@ -612,9 +617,8 @@ public:
     void sensesp(AstSenTree* nodep) { m_sensesp = nodep; }
     AstSenTree* sensesp() const { return m_sensesp; }
     // METHODS
-    inline bool hasInitial() const;
-    inline bool hasSettle() const;
     inline bool hasClocked() const;
+    inline bool hasCombo() const;
 };
 class AstArg final : public AstNode {
     // An argument to a function/task
@@ -626,7 +630,7 @@ public:
         , m_name{name} {
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_Arg;
+    ASTGEN_MEMBERS_AstArg;
     string name() const override { return m_name; }  // * = Pin name, ""=go by number
     void name(const string& name) override { m_name = name; }
     bool emptyConnectNoNext() const { return !exprp() && name() == "" && !nextp(); }
@@ -643,7 +647,7 @@ public:
         this->dimp(dimp);
         m_attrType = attrtype;
     }
-    ASTGEN_MEMBERS_AttrOf;
+    ASTGEN_MEMBERS_AstAttrOf;
     VAttrType attrType() const { return m_attrType; }
     void dump(std::ostream& str = std::cout) const override;
 };
@@ -660,7 +664,7 @@ public:
         UASSERT_OBJ(VN_IS(cellsp, Cell), cellsp, "Only instances allowed to be bound");
         this->addCellsp(cellsp);
     }
-    ASTGEN_MEMBERS_Bind;
+    ASTGEN_MEMBERS_AstBind;
     // ACCESSORS
     string name() const override { return m_name; }  // * = Bind Target name
     void name(const string& name) override { m_name = name; }
@@ -686,7 +690,6 @@ private:
     bool m_isTrace : 1;  // Function is related to tracing
     bool m_dontCombine : 1;  // V3Combine shouldn't compare this func tree, it's special
     bool m_declPrivate : 1;  // Declare it private
-    bool m_isFinal : 1;  // This is a function corresponding to a SystemVerilog 'final' block
     bool m_slow : 1;  // Slow routine, called once or just at init time
     bool m_funcPublic : 1;  // From user public task/function
     bool m_isConstructor : 1;  // Is C class constructor
@@ -715,7 +718,6 @@ public:
         m_isTrace = false;
         m_dontCombine = false;
         m_declPrivate = false;
-        m_isFinal = false;
         m_slow = false;
         m_funcPublic = false;
         m_isConstructor = false;
@@ -733,7 +735,7 @@ public:
         m_dpiImportWrapper = false;
         m_dpiTraceInit = false;
     }
-    ASTGEN_MEMBERS_CFunc;
+    ASTGEN_MEMBERS_AstCFunc;
     string name() const override { return m_name; }
     const char* broken() const override;
     void cloneRelink() override;
@@ -756,21 +758,20 @@ public:
     void isConst(VBoolOrUnknown flag) { m_isConst = flag; }
     bool isStatic() const { return m_isStatic; }
     void isStatic(bool flag) { m_isStatic = flag; }
-    bool isTrace() const { return m_isTrace; }
+    bool isTrace() const VL_MT_SAFE { return m_isTrace; }
     void isTrace(bool flag) { m_isTrace = flag; }
     void cname(const string& name) { m_cname = name; }
     string cname() const { return m_cname; }
     AstScope* scopep() const { return m_scopep; }
     void scopep(AstScope* nodep) { m_scopep = nodep; }
     string rtnTypeVoid() const { return ((m_rtnType == "") ? "void" : m_rtnType); }
+    void rtnType(const string& rtnType) { m_rtnType = rtnType; }
     bool dontCombine() const { return m_dontCombine || isTrace() || entryPoint(); }
     void dontCombine(bool flag) { m_dontCombine = flag; }
     bool dontInline() const { return dontCombine() || slow() || funcPublic(); }
     bool declPrivate() const { return m_declPrivate; }
     void declPrivate(bool flag) { m_declPrivate = flag; }
-    bool isFinal() const { return m_isFinal; }
-    void isFinal(bool flag) { m_isFinal = flag; }
-    bool slow() const { return m_slow; }
+    bool slow() const VL_MT_SAFE { return m_slow; }
     void slow(bool flag) { m_slow = flag; }
     bool funcPublic() const { return m_funcPublic; }
     void funcPublic(bool flag) { m_funcPublic = flag; }
@@ -799,16 +800,17 @@ public:
     void pure(bool flag) { m_pure = flag; }
     bool dpiContext() const { return m_dpiContext; }
     void dpiContext(bool flag) { m_dpiContext = flag; }
-    bool dpiExportDispatcher() const { return m_dpiExportDispatcher; }
+    bool dpiExportDispatcher() const VL_MT_SAFE { return m_dpiExportDispatcher; }
     void dpiExportDispatcher(bool flag) { m_dpiExportDispatcher = flag; }
     bool dpiExportImpl() const { return m_dpiExportImpl; }
     void dpiExportImpl(bool flag) { m_dpiExportImpl = flag; }
-    bool dpiImportPrototype() const { return m_dpiImportPrototype; }
+    bool dpiImportPrototype() const VL_MT_SAFE { return m_dpiImportPrototype; }
     void dpiImportPrototype(bool flag) { m_dpiImportPrototype = flag; }
     bool dpiImportWrapper() const { return m_dpiImportWrapper; }
     void dpiImportWrapper(bool flag) { m_dpiImportWrapper = flag; }
     void dpiTraceInit(bool flag) { m_dpiTraceInit = flag; }
     bool dpiTraceInit() const { return m_dpiTraceInit; }
+    bool isCoroutine() const { return m_rtnType == "VlCoroutine"; }
     // Special methods
     bool emptyBody() const {
         return argsp() == nullptr && initsp() == nullptr && stmtsp() == nullptr
@@ -827,7 +829,7 @@ public:
         : ASTGEN_SUPER_CUse(fl)
         , m_useType{useType}
         , m_name{name} {}
-    ASTGEN_MEMBERS_CUse;
+    ASTGEN_MEMBERS_AstCUse;
     void dump(std::ostream& str = std::cout) const override;
     string name() const override { return m_name; }
     VUseType useType() const { return m_useType; }
@@ -842,7 +844,7 @@ public:
         this->addCondsp(condsp);
         this->addStmtsp(stmtsp);
     }
-    ASTGEN_MEMBERS_CaseItem;
+    ASTGEN_MEMBERS_AstCaseItem;
     int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
     bool isDefault() const { return condsp() == nullptr; }
     bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); }
@@ -863,7 +865,7 @@ public:
         this->fromp(fromp);
         dtypeFrom(dtp);
     }
-    ASTGEN_MEMBERS_Cast;
+    ASTGEN_MEMBERS_AstCast;
     bool hasDType() const override { return true; }
     virtual string emitVerilog() { return "((%d)'(%l))"; }
     virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); }
@@ -882,7 +884,7 @@ public:
         this->lhsp(lhsp);
         this->dtp(dtp);
     }
-    ASTGEN_MEMBERS_CastParse;
+    ASTGEN_MEMBERS_AstCastParse;
     virtual string emitVerilog() { return "((%d)'(%l))"; }
     virtual string emitC() { V3ERROR_NA_RETURN(""); }
     virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); }
@@ -899,7 +901,7 @@ public:
         this->lhsp(lhsp);
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_CastSize;
+    ASTGEN_MEMBERS_AstCastSize;
     // No hasDType because widthing removes this node before the hasDType check
     virtual string emitVerilog() { return "((%r)'(%l))"; }
     virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); }
@@ -935,7 +937,7 @@ public:
         this->addParamsp(paramsp);
         this->rangep(rangep);
     }
-    ASTGEN_MEMBERS_Cell;
+    ASTGEN_MEMBERS_AstCell;
     // No cloneRelink, we presume cloneee's want the same module linkages
     void dump(std::ostream& str) const override;
     const char* broken() const override;
@@ -967,7 +969,7 @@ public:
         , m_name{name} {
         this->addSelp(selp);
     }
-    ASTGEN_MEMBERS_CellArrayRef;
+    ASTGEN_MEMBERS_AstCellArrayRef;
     // ACCESSORS
     string name() const override { return m_name; }  // * = Array name
 };
@@ -990,7 +992,7 @@ public:
         , m_name{name}
         , m_origModName{origModName}
         , m_timeunit{timeunit} {}
-    ASTGEN_MEMBERS_CellInline;
+    ASTGEN_MEMBERS_AstCellInline;
     void dump(std::ostream& str) const override;
     const char* broken() const override;
     // ACCESSORS
@@ -1015,7 +1017,7 @@ public:
         this->cellp(cellp);
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_CellRef;
+    ASTGEN_MEMBERS_AstCellRef;
     // ACCESSORS
     string name() const override { return m_name; }  // * = Array name
 };
@@ -1029,7 +1031,7 @@ public:
         : ASTGEN_SUPER_ClassExtends(fl) {
         this->classOrPkgsp(classOrPkgsp);  // Only for parser
     }
-    ASTGEN_MEMBERS_ClassExtends;
+    ASTGEN_MEMBERS_AstClassExtends;
     bool hasDType() const override { return true; }
     string verilogKwd() const override { return "extends"; }
     AstClass* classp() const;  // Class being extended (after link)
@@ -1048,7 +1050,7 @@ public:
         , m_classOrPackageNodep{classOrPackageNodep} {
         this->addParamsp(paramsp);
     }
-    ASTGEN_MEMBERS_ClassOrPackageRef;
+    ASTGEN_MEMBERS_AstClassOrPackageRef;
     // METHODS
     const char* broken() const override {
         BROKEN_RTN(m_classOrPackageNodep && !m_classOrPackageNodep->brokeExists());
@@ -1082,7 +1084,7 @@ public:
         this->addSensesp(sensesp);
         this->addBodysp(bodysp);
     }
-    ASTGEN_MEMBERS_Clocking;
+    ASTGEN_MEMBERS_AstClocking;
 };
 class AstConstPool final : public AstNode {
     // Container for const static data
@@ -1096,7 +1098,7 @@ class AstConstPool final : public AstNode {
 
 public:
     explicit AstConstPool(FileLine* fl);
-    ASTGEN_MEMBERS_ConstPool;
+    ASTGEN_MEMBERS_AstConstPool;
     bool maybePointedTo() const override { return true; }
     const char* broken() const override;
     void cloneRelink() override { V3ERROR_NA; }
@@ -1129,7 +1131,7 @@ public:
         this->rhsp(rhsp);
     }
     string name() const override { return m_name; }  // * = Scope name
-    ASTGEN_MEMBERS_DefParam;
+    ASTGEN_MEMBERS_AstDefParam;
     bool same(const AstNode*) const override { return true; }
     string path() const { return m_path; }
 };
@@ -1146,7 +1148,7 @@ public:
         this->lhsp(lhsp);
         this->rhsp(rhsp);
     }
-    ASTGEN_MEMBERS_Dot;
+    ASTGEN_MEMBERS_AstDot;
     // For parser, make only if non-null package
     static AstNode* newIfPkg(FileLine* fl, AstNode* packageOrClassp, AstNode* rhsp) {
         if (!packageOrClassp) return rhsp;
@@ -1167,7 +1169,7 @@ public:
         : ASTGEN_SUPER_DpiExport(fl)
         , m_name{vname}
         , m_cname{cname} {}
-    ASTGEN_MEMBERS_DpiExport;
+    ASTGEN_MEMBERS_AstDpiExport;
     string name() const override { return m_name; }
     void name(const string& name) override { m_name = name; }
     string cname() const { return m_cname; }
@@ -1181,7 +1183,7 @@ private:
 
 public:
     inline AstElabDisplay(FileLine* fl, VDisplayType dispType, AstNode* exprsp);
-    ASTGEN_MEMBERS_ElabDisplay;
+    ASTGEN_MEMBERS_AstElabDisplay;
     const char* broken() const override {
         BROKEN_RTN(!fmtp());
         return nullptr;
@@ -1204,7 +1206,7 @@ class AstEmpty final : public AstNode {
 public:
     explicit AstEmpty(FileLine* fl)
         : ASTGEN_SUPER_Empty(fl) {}
-    ASTGEN_MEMBERS_Empty;
+    ASTGEN_MEMBERS_AstEmpty;
     bool same(const AstNode* /*samep*/) const override { return true; }
 };
 class AstExecGraph final : public AstNode {
@@ -1223,7 +1225,7 @@ class AstExecGraph final : public AstNode {
 
 public:
     explicit AstExecGraph(FileLine* fl, const string& name);
-    ASTGEN_MEMBERS_ExecGraph;
+    ASTGEN_MEMBERS_AstExecGraph;
     ~AstExecGraph() override;
     const char* broken() const override {
         BROKEN_RTN(!m_depGraphp);
@@ -1242,7 +1244,7 @@ public:
         : ASTGEN_SUPER_Implicit(fl) {
         this->addExprsp(exprsp);
     }
-    ASTGEN_MEMBERS_Implicit;
+    ASTGEN_MEMBERS_AstImplicit;
 };
 class AstInitArray final : public AstNode {
     // Set a var to a map of values
@@ -1264,7 +1266,7 @@ public:
         dtypep(newDTypep);
         this->defaultp(defaultp);
     }
-    ASTGEN_MEMBERS_InitArray;
+    ASTGEN_MEMBERS_AstInitArray;
     void dump(std::ostream& str) const override;
     const char* broken() const override;
     void cloneRelink() override;
@@ -1291,7 +1293,7 @@ public:
         : ASTGEN_SUPER_InitItem(fl) {
         this->valuep(valuep);
     }
-    ASTGEN_MEMBERS_InitItem;
+    ASTGEN_MEMBERS_AstInitItem;
     bool maybePointedTo() const override { return true; }
     bool hasDType() const override { return false; }  // See valuep()'s dtype instead
 };
@@ -1304,7 +1306,7 @@ public:
         : ASTGEN_SUPER_IntfRef(fl)
         , m_name{name} {}
     string name() const override { return m_name; }
-    ASTGEN_MEMBERS_IntfRef;
+    ASTGEN_MEMBERS_AstIntfRef;
 };
 class AstMTaskBody final : public AstNode {
     // Hold statements for each MTask
@@ -1314,7 +1316,7 @@ class AstMTaskBody final : public AstNode {
 public:
     explicit AstMTaskBody(FileLine* fl)
         : ASTGEN_SUPER_MTaskBody(fl) {}
-    ASTGEN_MEMBERS_MTaskBody;
+    ASTGEN_MEMBERS_AstMTaskBody;
     const char* broken() const override {
         BROKEN_RTN(!m_execMTaskp);
         return nullptr;
@@ -1342,7 +1344,7 @@ public:
     }
     string name() const override { return m_name; }
     bool maybePointedTo() const override { return true; }
-    ASTGEN_MEMBERS_Modport;
+    ASTGEN_MEMBERS_AstModport;
 };
 class AstModportFTaskRef final : public AstNode {
     // An import/export referenced under a modport
@@ -1358,7 +1360,7 @@ public:
         : ASTGEN_SUPER_ModportFTaskRef(fl)
         , m_name{name}
         , m_export{isExport} {}
-    ASTGEN_MEMBERS_ModportFTaskRef;
+    ASTGEN_MEMBERS_AstModportFTaskRef;
     const char* broken() const override;
     void cloneRelink() override;
     void dump(std::ostream& str) const override;
@@ -1381,7 +1383,7 @@ public:
         : ASTGEN_SUPER_ModportVarRef(fl)
         , m_name{name}
         , m_direction{direction} {}
-    ASTGEN_MEMBERS_ModportVarRef;
+    ASTGEN_MEMBERS_AstModportVarRef;
     const char* broken() const override;
     void cloneRelink() override;
     void dump(std::ostream& str) const override;
@@ -1403,18 +1405,19 @@ class AstNetlist final : public AstNode {
     AstConstPool* const m_constPoolp;  // Reference to constant pool, for faster lookup
     AstPackage* m_dollarUnitPkgp = nullptr;  // $unit
     AstCFunc* m_evalp = nullptr;  // The '_eval' function
+    AstCFunc* m_evalNbap = nullptr;  // The '_eval__nba' function
     AstVarScope* m_dpiExportTriggerp = nullptr;  // The DPI export trigger variable
+    AstVar* m_delaySchedulerp = nullptr;  // The delay scheduler variable
     AstTopScope* m_topScopep = nullptr;  // The singleton AstTopScope under the top module
     VTimescale m_timeunit;  // Global time unit
     VTimescale m_timeprecision;  // Global time precision
-    bool m_changeRequest = false;  // Have _change_request method
     bool m_timescaleSpecified = false;  // Input HDL specified timescale
     uint32_t m_nextFreeMTaskID = 1;  // Next unique MTask ID within netlist
                                      // starts at 1 so 0 means no MTask ID
     uint32_t m_nextFreeMTaskProfilingID = 0;  // Next unique ID to use for PGO
 public:
     AstNetlist();
-    ASTGEN_MEMBERS_Netlist;
+    ASTGEN_MEMBERS_AstNetlist;
     const char* broken() const override;
     void cloneRelink() override { V3ERROR_NA; }
     string name() const override { return "$root"; }
@@ -1423,15 +1426,17 @@ public:
         return modulesp();  // First one in the list, for now
     }
     AstTypeTable* typeTablep() { return m_typeTablep; }
-    void changeRequest(bool specified) { m_changeRequest = specified; }
-    bool changeRequest() const { return m_changeRequest; }
     AstConstPool* constPoolp() { return m_constPoolp; }
     AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; }
     AstPackage* dollarUnitPkgAddp();
     AstCFunc* evalp() const { return m_evalp; }
-    void evalp(AstCFunc* evalp) { m_evalp = evalp; }
+    void evalp(AstCFunc* funcp) { m_evalp = funcp; }
+    AstCFunc* evalNbap() const { return m_evalNbap; }
+    void evalNbap(AstCFunc* funcp) { m_evalNbap = funcp; }
     AstVarScope* dpiExportTriggerp() const { return m_dpiExportTriggerp; }
     void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; }
+    AstVar* delaySchedulerp() const { return m_delaySchedulerp; }
+    void delaySchedulerp(AstVar* const varScopep) { m_delaySchedulerp = varScopep; }
     AstTopScope* topScopep() const { return m_topScopep; }
     void createTopScope(AstScope* scopep);
     VTimescale timeunit() const { return m_timeunit; }
@@ -1458,7 +1463,7 @@ public:
         : ASTGEN_SUPER_PackageExport(fl)
         , m_name{name}
         , m_packagep{packagep} {}
-    ASTGEN_MEMBERS_PackageExport;
+    ASTGEN_MEMBERS_AstPackageExport;
     const char* broken() const override;
     void cloneRelink() override;
     void dump(std::ostream& str) const override;
@@ -1472,7 +1477,7 @@ public:
     // cppcheck-suppress noExplicitConstructor
     AstPackageExportStarStar(FileLine* fl)
         : ASTGEN_SUPER_PackageExportStarStar(fl) {}
-    ASTGEN_MEMBERS_PackageExportStarStar;
+    ASTGEN_MEMBERS_AstPackageExportStarStar;
 };
 class AstPackageImport final : public AstNode {
 private:
@@ -1484,7 +1489,7 @@ public:
         : ASTGEN_SUPER_PackageImport(fl)
         , m_name{name}
         , m_packagep{packagep} {}
-    ASTGEN_MEMBERS_PackageImport;
+    ASTGEN_MEMBERS_AstPackageImport;
     const char* broken() const override;
     void cloneRelink() override;
     void dump(std::ostream& str) const override;
@@ -1512,7 +1517,7 @@ public:
         this->lhsp(lhsp);
         this->ftaskrefp(ftaskrefp);
     }
-    ASTGEN_MEMBERS_ParseRef;
+    ASTGEN_MEMBERS_AstParseRef;
     void dump(std::ostream& str) const override;
     string name() const override { return m_name; }  // * = Var name
     bool same(const AstNode* samep) const override {
@@ -1541,7 +1546,7 @@ public:
         this->exprp(exprp);
     }
     inline AstPin(FileLine* fl, int pinNum, AstVarRef* varname, AstNode* exprp);
-    ASTGEN_MEMBERS_Pin;
+    ASTGEN_MEMBERS_AstPin;
     void dump(std::ostream& str) const override;
     const char* broken() const override;
     string name() const override { return m_name; }  // * = Pin name, ""=go by number
@@ -1569,7 +1574,7 @@ public:
         : ASTGEN_SUPER_Port(fl)
         , m_pinNum{pinnum}
         , m_name{name} {}
-    ASTGEN_MEMBERS_Port;
+    ASTGEN_MEMBERS_AstPort;
     string name() const override { return m_name; }  // * = Port name
     int pinNum() const { return m_pinNum; }  // * = Pin number, for order based instantiation
 };
@@ -1581,7 +1586,7 @@ public:
     AstPragma(FileLine* fl, VPragmaType pragType)
         : ASTGEN_SUPER_Pragma(fl)
         , m_pragType{pragType} {}
-    ASTGEN_MEMBERS_Pragma;
+    ASTGEN_MEMBERS_AstPragma;
     VPragmaType pragType() const { return m_pragType; }  // *=type of the pragma
     bool isPredictOptimizable() const override { return false; }
     bool same(const AstNode* samep) const override {
@@ -1602,7 +1607,7 @@ public:
         this->disablep(disablep);
         this->propp(propp);
     }
-    ASTGEN_MEMBERS_PropClocked;
+    ASTGEN_MEMBERS_AstPropClocked;
     bool hasDType() const override {
         return true;
     }  // Used under Cover, which expects a bool child
@@ -1618,7 +1623,7 @@ public:
         , m_direction{direction} {
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_Pull;
+    ASTGEN_MEMBERS_AstPull;
     bool same(const AstNode* samep) const override {
         return direction() == static_cast(samep)->direction();
     }
@@ -1656,7 +1661,7 @@ public:
         dtypeSetString();
         addExprsp(exprsp);
     }
-    ASTGEN_MEMBERS_SFormatF;
+    ASTGEN_MEMBERS_AstSFormatF;
     string name() const override { return m_text; }
     int instrCount() const override { return INSTR_COUNT_PLI; }
     bool hasDType() const override { return true; }
@@ -1682,7 +1687,6 @@ class AstScope final : public AstNode {
     // Children: NODEBLOCK
     // @astgen op1 := varsp : List[AstVarScope]
     // @astgen op2 := blocksp : List[AstNode] // Logic blocks/AstActive/AstCFunc
-    // @astgen op3 := finalClksp : List[AstNode]
 
     // An AstScope->name() is special: . indicates an uninlined scope, __DOT__ an inlined scope
     string m_name;  // Name
@@ -1697,7 +1701,7 @@ public:
         , m_aboveScopep{aboveScopep}
         , m_aboveCellp{aboveCellp}
         , m_modp{modp} {}
-    ASTGEN_MEMBERS_Scope;
+    ASTGEN_MEMBERS_AstScope;
     void cloneRelink() override;
     const char* broken() const override;
     bool maybePointedTo() const override { return true; }
@@ -1707,9 +1711,14 @@ public:
     string nameDotless() const;
     string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); }
     AstNodeModule* modp() const { return m_modp; }
-    AstScope* aboveScopep() const { return m_aboveScopep; }
+    //
+    AstScope* aboveScopep() const VL_MT_SAFE { return m_aboveScopep; }
     AstCell* aboveCellp() const { return m_aboveCellp; }
-    bool isTop() const { return aboveScopep() == nullptr; }  // At top of hierarchy
+    bool isTop() const VL_MT_SAFE { return aboveScopep() == nullptr; }  // At top of hierarchy
+    // Create new MODULETEMP variable under this scope
+    AstVarScope* createTemp(const string& name, unsigned width);
+    AstVarScope* createTemp(const string& name, AstNodeDType* dtypep);
+    AstVarScope* createTempLike(const string& name, AstVarScope* vscp);
 };
 class AstSelLoopVars final : public AstNode {
     // Parser only concept "[id, id, id]" for a foreach statement
@@ -1722,7 +1731,7 @@ public:
         this->fromp(fromp);
         this->addElementsp(elementsp);
     }
-    ASTGEN_MEMBERS_SelLoopVars;
+    ASTGEN_MEMBERS_AstSelLoopVars;
     bool same(const AstNode* /*samep*/) const override { return true; }
     bool maybePointedTo() const override { return false; }
 };
@@ -1731,11 +1740,12 @@ class AstSenItem final : public AstNode {
     // @astgen op1 := sensp : Optional[AstNode] // Sensitivity expression
     VEdgeType m_edgeType;  // Edge type
 public:
-    class Combo {};  // for creator type-overload selection
-    class Illegal {};  // for creator type-overload selection
-    class Initial {};  // for creator type-overload selection
-    class Settle {};  // for creator type-overload selection
-    class Never {};  // for creator type-overload selection
+    class Combo {};  // for constructor type-overload selection
+    class Illegal {};  // for constructor type-overload selection
+    class Static {};  // for constructor type-overload selection
+    class Initial {};  // for constructor type-overload selection
+    class Final {};  // for constructor type-overload selection
+    class Never {};  // for constructor type-overload selection
     AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* senp)
         : ASTGEN_SUPER_SenItem(fl)
         , m_edgeType{edgeType} {
@@ -1747,21 +1757,24 @@ public:
     AstSenItem(FileLine* fl, Illegal)
         : ASTGEN_SUPER_SenItem(fl)
         , m_edgeType{VEdgeType::ET_ILLEGAL} {}
+    AstSenItem(FileLine* fl, Static)
+        : ASTGEN_SUPER_SenItem(fl)
+        , m_edgeType{VEdgeType::ET_STATIC} {}
     AstSenItem(FileLine* fl, Initial)
         : ASTGEN_SUPER_SenItem(fl)
         , m_edgeType{VEdgeType::ET_INITIAL} {}
-    AstSenItem(FileLine* fl, Settle)
+    AstSenItem(FileLine* fl, Final)
         : ASTGEN_SUPER_SenItem(fl)
-        , m_edgeType{VEdgeType::ET_SETTLE} {}
+        , m_edgeType{VEdgeType::ET_FINAL} {}
     AstSenItem(FileLine* fl, Never)
         : ASTGEN_SUPER_SenItem(fl)
         , m_edgeType{VEdgeType::ET_NEVER} {}
-    ASTGEN_MEMBERS_SenItem;
+    ASTGEN_MEMBERS_AstSenItem;
     void dump(std::ostream& str) const override;
     bool same(const AstNode* samep) const override {
         return edgeType() == static_cast(samep)->edgeType();
     }
-    VEdgeType edgeType() const { return m_edgeType; }  // * = Posedge/negedge
+    VEdgeType edgeType() const { return m_edgeType; }
     void edgeType(VEdgeType type) {
         m_edgeType = type;
         editCountInc();
@@ -1770,11 +1783,12 @@ public:
     //
     bool isClocked() const { return edgeType().clockedStmt(); }
     bool isCombo() const { return edgeType() == VEdgeType::ET_COMBO; }
+    bool isHybrid() const { return edgeType() == VEdgeType::ET_HYBRID; }
+    bool isStatic() const { return edgeType() == VEdgeType::ET_STATIC; }
     bool isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; }
+    bool isFinal() const { return edgeType() == VEdgeType::ET_FINAL; }
     bool isIllegal() const { return edgeType() == VEdgeType::ET_ILLEGAL; }
-    bool isSettle() const { return edgeType() == VEdgeType::ET_SETTLE; }
     bool isNever() const { return edgeType() == VEdgeType::ET_NEVER; }
-    bool hasVar() const { return !(isCombo() || isInitial() || isSettle() || isNever()); }
 };
 class AstSenTree final : public AstNode {
     // A sensitivity list
@@ -1785,23 +1799,25 @@ public:
         : ASTGEN_SUPER_SenTree(fl) {
         this->addSensesp(sensesp);
     }
-    ASTGEN_MEMBERS_SenTree;
+    ASTGEN_MEMBERS_AstSenTree;
     void dump(std::ostream& str) const override;
     bool maybePointedTo() const override { return true; }
     bool isMulti() const { return m_multi; }
     void multi(bool flag) { m_multi = true; }
     // METHODS
     bool hasClocked() const;  // Includes a clocked statement
-    bool hasSettle() const;  // Includes a SETTLE SenItem
+    bool hasStatic() const;  // Includes a STATIC SenItem
     bool hasInitial() const;  // Includes a INITIAL SenItem
+    bool hasFinal() const;  // Includes a FINAL SenItem
     bool hasCombo() const;  // Includes a COMBO SenItem
+    bool hasHybrid() const;  // Includes a HYBRID SenItem
 };
 class AstSplitPlaceholder final : public AstNode {
 public:
     // Dummy node used within V3Split; never exists outside of V3Split.
     explicit AstSplitPlaceholder(FileLine* fl)
         : ASTGEN_SUPER_SplitPlaceholder(fl) {}
-    ASTGEN_MEMBERS_SplitPlaceholder;
+    ASTGEN_MEMBERS_AstSplitPlaceholder;
 };
 class AstStrengthSpec final : public AstNode {
 private:
@@ -1814,7 +1830,7 @@ public:
         , m_s0{s0}
         , m_s1{s1} {}
 
-    ASTGEN_MEMBERS_StrengthSpec;
+    ASTGEN_MEMBERS_AstStrengthSpec;
     VStrength strength0() { return m_s0; }
     VStrength strength1() { return m_s1; }
     void dump(std::ostream& str) const override;
@@ -1834,7 +1850,7 @@ class AstTopScope final : public AstNode {
     }
 
 public:
-    ASTGEN_MEMBERS_TopScope;
+    ASTGEN_MEMBERS_AstTopScope;
     bool maybePointedTo() const override { return true; }
 };
 class AstTypeTable final : public AstNode {
@@ -1850,7 +1866,7 @@ class AstTypeTable final : public AstNode {
 
 public:
     explicit AstTypeTable(FileLine* fl);
-    ASTGEN_MEMBERS_TypeTable;
+    ASTGEN_MEMBERS_AstTypeTable;
     bool maybePointedTo() const override { return true; }
     const char* broken() const override {
         BROKEN_RTN(m_emptyQueuep && !m_emptyQueuep->brokeExists());
@@ -1888,7 +1904,7 @@ public:
         addAttrsp(attrsp);
         dtypep(nullptr);  // V3Width will resolve
     }
-    ASTGEN_MEMBERS_Typedef;
+    ASTGEN_MEMBERS_AstTypedef;
     void dump(std::ostream& str) const override;
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
     virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); }
@@ -1911,7 +1927,7 @@ public:
     AstTypedefFwd(FileLine* fl, const string& name)
         : ASTGEN_SUPER_TypedefFwd(fl)
         , m_name{name} {}
-    ASTGEN_MEMBERS_TypedefFwd;
+    ASTGEN_MEMBERS_AstTypedefFwd;
     // METHODS
     string name() const override { return m_name; }
     bool maybePointedTo() const override { return true; }
@@ -1923,7 +1939,7 @@ public:
         : ASTGEN_SUPER_UdpTable(fl) {
         this->addLinesp(linesp);
     }
-    ASTGEN_MEMBERS_UdpTable;
+    ASTGEN_MEMBERS_AstUdpTable;
 };
 class AstUdpTableLine final : public AstNode {
     string m_text;
@@ -1932,7 +1948,7 @@ public:
     AstUdpTableLine(FileLine* fl, const string& text)
         : ASTGEN_SUPER_UdpTableLine(fl)
         , m_text{text} {}
-    ASTGEN_MEMBERS_UdpTableLine;
+    ASTGEN_MEMBERS_AstUdpTableLine;
     string name() const override { return m_text; }
     string text() const { return m_text; }
 };
@@ -1949,13 +1965,13 @@ public:
         this->refp(refp);
         this->cellrefp(cellrefp);
     }
-    ASTGEN_MEMBERS_UnlinkedRef;
+    ASTGEN_MEMBERS_AstUnlinkedRef;
 };
 class AstVar final : public AstNode {
     // A variable (in/out/wire/reg/param) inside a module
     //
     // @astgen op1 := childDTypep : Optional[AstNodeDType]
-    // @astgen op2 := delayp : Optional[AstNode] // Net delay
+    // @astgen op2 := delayp : Optional[AstDelay] // Net delay
     // Initial value that never changes (static const), or constructor argument for
     // MTASKSTATE variables
     // @astgen op3 := valuep : Optional[AstNode]
@@ -1986,9 +2002,9 @@ class AstVar final : public AstNode {
     bool m_usedClock : 1;  // Signal used as a clock
     bool m_usedParam : 1;  // Parameter is referenced (on link; later signals not setup)
     bool m_usedLoopIdx : 1;  // Variable subject of for unrolling
+    bool m_usedVirtIface : 1;  // Signal used through a virtual interface
     bool m_funcLocal : 1;  // Local variable for a function
     bool m_funcReturn : 1;  // Return variable for a function
-    bool m_attrClockEn : 1;  // User clock enable attribute
     bool m_attrScBv : 1;  // User force bit vector attribute
     bool m_attrIsolateAssign : 1;  // User isolate_assignments attribute
     bool m_attrSFormat : 1;  // User sformat attribute
@@ -2011,6 +2027,8 @@ class AstVar final : public AstNode {
     bool m_trace : 1;  // Trace this variable
     bool m_isLatched : 1;  // Not assigned in all control paths of combo always
     bool m_isForceable : 1;  // May be forced/released externally from user C code
+    bool m_isWrittenByDpi : 1;  // This variable can be written by a DPI Export
+    bool m_isWrittenBySuspendable : 1;  // This variable can be written by a suspendable process
 
     void init() {
         m_ansi = false;
@@ -2023,13 +2041,13 @@ class AstVar final : public AstNode {
         m_usedClock = false;
         m_usedParam = false;
         m_usedLoopIdx = false;
+        m_usedVirtIface = false;
         m_sigPublic = false;
         m_sigModPublic = false;
         m_sigUserRdPublic = false;
         m_sigUserRWPublic = false;
         m_funcLocal = false;
         m_funcReturn = false;
-        m_attrClockEn = false;
         m_attrScBv = false;
         m_attrIsolateAssign = false;
         m_attrSFormat = false;
@@ -2052,6 +2070,8 @@ class AstVar final : public AstNode {
         m_trace = false;
         m_isLatched = false;
         m_isForceable = false;
+        m_isWrittenByDpi = false;
+        m_isWrittenBySuspendable = false;
         m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN;
     }
 
@@ -2112,20 +2132,20 @@ public:
         dtypeFrom(examplep);
         m_declKwd = examplep->declKwd();
     }
-    ASTGEN_MEMBERS_Var;
+    ASTGEN_MEMBERS_AstVar;
     void dump(std::ostream& str) const override;
-    string name() const override { return m_name; }  // * = Var name
+    string name() const override VL_MT_SAFE { return m_name; }  // * = Var name
     bool hasDType() const override { return true; }
     bool maybePointedTo() const override { return true; }
     string origName() const override { return m_origName; }  // * = Original name
     void origName(const string& name) { m_origName = name; }
-    VVarType varType() const { return m_varType; }  // * = Type of variable
+    VVarType varType() const VL_MT_SAFE { return m_varType; }  // * = Type of variable
     void direction(const VDirection& flag) {
         m_direction = flag;
         if (m_direction == VDirection::INOUT) m_tristate = true;
     }
-    VDirection direction() const { return m_direction; }
-    bool isIO() const { return m_direction != VDirection::NONE; }
+    VDirection direction() const VL_MT_SAFE { return m_direction; }
+    bool isIO() const VL_MT_SAFE { return m_direction != VDirection::NONE; }
     void declDirection(const VDirection& flag) { m_declDirection = flag; }
     VDirection declDirection() const { return m_declDirection; }
     void varType(VVarType type) { m_varType = type; }
@@ -2145,20 +2165,21 @@ public:
     string dpiTmpVarType(const string& varName) const;
     // Return Verilator internal type for argument: CData, SData, IData, WData
     string vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc = "",
-                     bool asRef = false) const;
+                     bool asRef = false) const VL_MT_SAFE;
     string vlEnumType() const;  // Return VerilatorVarType: VLVT_UINT32, etc
     string vlEnumDir() const;  // Return VerilatorVarDir: VLVD_INOUT, etc
     string vlPropDecl(const string& propName) const;  // Return VerilatorVarProps declaration
     void combineType(VVarType type);
     AstNodeDType* getChildDTypep() const override { return childDTypep(); }
-    AstNodeDType* dtypeSkipRefp() const { return subDTypep()->skipRefp(); }
+    AstNodeDType* dtypeSkipRefp() const VL_MT_SAFE { return subDTypep()->skipRefp(); }
     // (Slow) recurse down to find basic data type (Note don't need virtual -
     // AstVar isn't a NodeDType)
-    AstBasicDType* basicp() const { return subDTypep()->basicp(); }
-    virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); }
+    AstBasicDType* basicp() const VL_MT_SAFE { return subDTypep()->basicp(); }
+    virtual AstNodeDType* subDTypep() const VL_MT_SAFE {
+        return dtypep() ? dtypep() : childDTypep();
+    }
     void ansi(bool flag) { m_ansi = flag; }
     void declTyped(bool flag) { m_declTyped = flag; }
-    void attrClockEn(bool flag) { m_attrClockEn = flag; }
     void attrClocker(VVarAttrClocker flag) { m_attrClocker = flag; }
     void attrFileDescr(bool flag) { m_fileDescr = flag; }
     void attrScClocked(bool flag) { m_scClocked = flag; }
@@ -2169,6 +2190,7 @@ public:
     void usedClock(bool flag) { m_usedClock = flag; }
     void usedParam(bool flag) { m_usedParam = flag; }
     void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; }
+    void usedVirtIface(bool flag) { m_usedVirtIface = flag; }
     void sigPublic(bool flag) { m_sigPublic = flag; }
     void sigModPublic(bool flag) { m_sigModPublic = flag; }
     void sigUserRdPublic(bool flag) {
@@ -2192,7 +2214,7 @@ public:
     void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; }
     bool hasStrengthAssignment() { return m_hasStrengthAssignment; }
     void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; }
-    bool isDpiOpenArray() const { return m_isDpiOpenArray; }
+    bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; }
     bool isHideLocal() const { return m_isHideLocal; }
     void isHideLocal(bool flag) { m_isHideLocal = flag; }
     bool isHideProtected() const { return m_isHideProtected; }
@@ -2207,6 +2229,11 @@ public:
     void isLatched(bool flag) { m_isLatched = flag; }
     bool isForceable() const { return m_isForceable; }
     void setForceable() { m_isForceable = true; }
+    bool isWrittenByDpi() const { return m_isWrittenByDpi; }
+    void setWrittenByDpi() { m_isWrittenByDpi = true; }
+    bool isWrittenBySuspendable() const { return m_isWrittenBySuspendable; }
+    void setWrittenBySuspendable() { m_isWrittenBySuspendable = true; }
+
     // METHODS
     void name(const string& name) override { m_name = name; }
     void tag(const string& text) override { m_tag = text; }
@@ -2216,8 +2243,8 @@ public:
     bool isDeclTyped() const { return m_declTyped; }
     bool isInoutish() const { return m_direction.isInoutish(); }
     bool isNonOutput() const { return m_direction.isNonOutput(); }
-    bool isReadOnly() const { return m_direction.isReadOnly(); }
-    bool isWritable() const { return m_direction.isWritable(); }
+    bool isReadOnly() const VL_MT_SAFE { return m_direction.isReadOnly(); }
+    bool isWritable() const VL_MT_SAFE { return m_direction.isWritable(); }
     bool isTristate() const { return m_tristate; }
     bool isPrimaryIO() const { return m_primaryIO; }
     bool isPrimaryInish() const { return isPrimaryIO() && isNonOutput(); }
@@ -2235,7 +2262,7 @@ public:
     bool isClassMember() const { return varType() == VVarType::MEMBER; }
     bool isStatementTemp() const { return (varType() == VVarType::STMTTEMP); }
     bool isXTemp() const { return (varType() == VVarType::XTEMP); }
-    bool isParam() const {
+    bool isParam() const VL_MT_SAFE {
         return (varType() == VVarType::LPARAM || varType() == VVarType::GPARAM);
     }
     bool isGParam() const { return (varType() == VVarType::GPARAM); }
@@ -2247,7 +2274,8 @@ public:
     bool isUsedClock() const { return m_usedClock; }
     bool isUsedParam() const { return m_usedParam; }
     bool isUsedLoopIdx() const { return m_usedLoopIdx; }
-    bool isSc() const { return m_sc; }
+    bool isUsedVirtIface() const { return m_usedVirtIface; }
+    bool isSc() const VL_MT_SAFE { return m_sc; }
     bool isScQuad() const;
     bool isScBv() const;
     bool isScUint() const;
@@ -2259,14 +2287,13 @@ public:
     bool isSigUserRWPublic() const { return m_sigUserRWPublic; }
     bool isTrace() const { return m_trace; }
     bool isRand() const { return m_isRand; }
-    bool isConst() const { return m_isConst; }
-    bool isStatic() const { return m_isStatic; }
+    bool isConst() const VL_MT_SAFE { return m_isConst; }
+    bool isStatic() const VL_MT_SAFE { return m_isStatic; }
     bool isLatched() const { return m_isLatched; }
     bool isFuncLocal() const { return m_funcLocal; }
     bool isFuncReturn() const { return m_funcReturn; }
     bool isPullup() const { return m_isPullup; }
     bool isPulldown() const { return m_isPulldown; }
-    bool attrClockEn() const { return m_attrClockEn; }
     bool attrScBv() const { return m_attrScBv; }
     bool attrFileDescr() const { return m_fileDescr; }
     bool attrScClocked() const { return m_scClocked; }
@@ -2280,14 +2307,13 @@ public:
     void propagateAttrFrom(AstVar* fromp) {
         // This is getting connected to fromp; keep attributes
         // Note the method below too
-        if (fromp->attrClockEn()) attrClockEn(true);
         if (fromp->attrFileDescr()) attrFileDescr(true);
         if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
         if (fromp->isContinuously()) isContinuously(true);
     }
     bool gateMultiInputOptimizable() const {
         // Ok to gate optimize; must return false if propagateAttrFrom would do anything
-        return (!attrClockEn() && !isUsedClock());
+        return !isUsedClock();
     }
     void combineType(AstVar* typevarp) {
         // This is same as typevarp (for combining input & reg decls)
@@ -2323,7 +2349,6 @@ class AstVarScope final : public AstNode {
 private:
     AstScope* m_scopep;  // Scope variable is underneath
     AstVar* m_varp;  // [AfterLink] Pointer to variable itself
-    bool m_circular : 1;  // Used in circular ordering dependency, need change detect
     bool m_trace : 1;  // Tracing is turned on for this scope
 public:
     AstVarScope(FileLine* fl, AstScope* scopep, AstVar* varp)
@@ -2332,11 +2357,10 @@ public:
         , m_varp{varp} {
         UASSERT_OBJ(scopep, fl, "Scope must be non-null");
         UASSERT_OBJ(varp, fl, "Var must be non-null");
-        m_circular = false;
         m_trace = true;
         dtypeFrom(varp);
     }
-    ASTGEN_MEMBERS_VarScope;
+    ASTGEN_MEMBERS_AstVarScope;
     void cloneRelink() override {
         if (m_varp && m_varp->clonep()) {
             m_varp = m_varp->clonep();
@@ -2356,8 +2380,6 @@ public:
     AstVar* varp() const { return m_varp; }  // [After Link] Pointer to variable
     AstScope* scopep() const { return m_scopep; }  // Pointer to scope it's under
     void scopep(AstScope* nodep) { m_scopep = nodep; }
-    bool isCircular() const { return m_circular; }
-    void circular(bool flag) { m_circular = flag; }
     bool isTrace() const { return m_trace; }
     void trace(bool flag) { m_trace = flag; }
 };
@@ -2377,7 +2399,7 @@ public:
         : ASTGEN_SUPER_Begin(fl, name, stmtsp)
         , m_generate{generate}
         , m_implied{implied} {}
-    ASTGEN_MEMBERS_Begin;
+    ASTGEN_MEMBERS_AstBegin;
     void dump(std::ostream& str) const override;
     void generate(bool flag) { m_generate = flag; }
     bool generate() const { return m_generate; }
@@ -2393,7 +2415,8 @@ public:
     // Node that puts name into the output stream
     AstFork(FileLine* fl, const string& name, AstNode* stmtsp)
         : ASTGEN_SUPER_Fork(fl, name, stmtsp) {}
-    ASTGEN_MEMBERS_Fork;
+    ASTGEN_MEMBERS_AstFork;
+    bool isTimingControl() const override { return !joinType().joinNone(); }
     void dump(std::ostream& str) const override;
     VJoinType joinType() const { return m_joinType; }
     void joinType(const VJoinType& flag) { m_joinType = flag; }
@@ -2407,7 +2430,7 @@ public:
         : ASTGEN_SUPER_Func(fl, name, stmtp) {
         this->fvarp(fvarp);
     }
-    ASTGEN_MEMBERS_Func;
+    ASTGEN_MEMBERS_AstFunc;
     bool hasDType() const override { return true; }
 };
 class AstTask final : public AstNodeFTask {
@@ -2415,7 +2438,7 @@ class AstTask final : public AstNodeFTask {
 public:
     AstTask(FileLine* fl, const string& name, AstNode* stmtp)
         : ASTGEN_SUPER_Task(fl, name, stmtp) {}
-    ASTGEN_MEMBERS_Task;
+    ASTGEN_MEMBERS_AstTask;
 };
 
 // === AstNodeFile ===
@@ -2432,14 +2455,14 @@ public:
         , m_slow{false}
         , m_source{false}
         , m_support{false} {}
-    ASTGEN_MEMBERS_CFile;
+    ASTGEN_MEMBERS_AstCFile;
     void dump(std::ostream& str = std::cout) const override;
     bool slow() const { return m_slow; }
     void slow(bool flag) { m_slow = flag; }
     bool source() const { return m_source; }
     void source(bool flag) { m_source = flag; }
     bool support() const { return m_support; }
-    void support(bool flag) { m_support = flag; }
+    void support(bool flag) VL_MT_SAFE { m_support = flag; }
 };
 class AstVFile final : public AstNodeFile {
     // Verilog output file
@@ -2447,7 +2470,7 @@ class AstVFile final : public AstNodeFile {
 public:
     AstVFile(FileLine* fl, const string& name)
         : ASTGEN_SUPER_VFile(fl, name) {}
-    ASTGEN_MEMBERS_VFile;
+    ASTGEN_MEMBERS_AstVFile;
     void dump(std::ostream& str = std::cout) const override;
 };
 
@@ -2466,14 +2489,14 @@ class AstClass final : public AstNodeModule {
 public:
     AstClass(FileLine* fl, const string& name)
         : ASTGEN_SUPER_Class(fl, name) {}
-    ASTGEN_MEMBERS_Class;
+    ASTGEN_MEMBERS_AstClass;
     string verilogKwd() const override { return "class"; }
     bool maybePointedTo() const override { return true; }
     void dump(std::ostream& str) const override;
     const char* broken() const override;
     void cloneRelink() override;
     bool timescaleMatters() const override { return false; }
-    AstClassPackage* classOrPackagep() const { return m_classOrPackagep; }
+    AstClassPackage* classOrPackagep() const VL_MT_SAFE { return m_classOrPackagep; }
     void classOrPackagep(AstClassPackage* classpackagep) { m_classOrPackagep = classpackagep; }
     AstNode* membersp() const { return stmtsp(); }
     void addMembersp(AstNode* nodep) {
@@ -2501,12 +2524,12 @@ class AstClassPackage final : public AstNodeModule {
 public:
     AstClassPackage(FileLine* fl, const string& name)
         : ASTGEN_SUPER_ClassPackage(fl, name) {}
-    ASTGEN_MEMBERS_ClassPackage;
+    ASTGEN_MEMBERS_AstClassPackage;
     string verilogKwd() const override { return "classpackage"; }
     const char* broken() const override;
     void cloneRelink() override;
     bool timescaleMatters() const override { return false; }
-    AstClass* classp() const { return m_classp; }
+    AstClass* classp() const VL_MT_SAFE { return m_classp; }
     void classp(AstClass* classp) { m_classp = classp; }
 };
 class AstIface final : public AstNodeModule {
@@ -2514,7 +2537,7 @@ class AstIface final : public AstNodeModule {
 public:
     AstIface(FileLine* fl, const string& name)
         : ASTGEN_SUPER_Iface(fl, name) {}
-    ASTGEN_MEMBERS_Iface;
+    ASTGEN_MEMBERS_AstIface;
     // Interfaces have `timescale applicability but lots of code seems to
     // get false warnings if we enable this
     string verilogKwd() const override { return "interface"; }
@@ -2528,7 +2551,7 @@ public:
     AstModule(FileLine* fl, const string& name, bool program = false)
         : ASTGEN_SUPER_Module(fl, name)
         , m_isProgram{program} {}
-    ASTGEN_MEMBERS_Module;
+    ASTGEN_MEMBERS_AstModule;
     string verilogKwd() const override { return m_isProgram ? "program" : "module"; }
     bool timescaleMatters() const override { return true; }
 };
@@ -2537,7 +2560,7 @@ class AstNotFoundModule final : public AstNodeModule {
 public:
     AstNotFoundModule(FileLine* fl, const string& name)
         : ASTGEN_SUPER_NotFoundModule(fl, name) {}
-    ASTGEN_MEMBERS_NotFoundModule;
+    ASTGEN_MEMBERS_AstNotFoundModule;
     string verilogKwd() const override { return "/*not-found-*/ module"; }
     bool timescaleMatters() const override { return false; }
 };
@@ -2546,7 +2569,7 @@ class AstPackage final : public AstNodeModule {
 public:
     AstPackage(FileLine* fl, const string& name)
         : ASTGEN_SUPER_Package(fl, name) {}
-    ASTGEN_MEMBERS_Package;
+    ASTGEN_MEMBERS_AstPackage;
     string verilogKwd() const override { return "package"; }
     bool timescaleMatters() const override { return !isDollarUnit(); }
     static string dollarUnitName() { return AstNode::encodeName("$unit"); }
@@ -2557,7 +2580,7 @@ class AstPrimitive final : public AstNodeModule {
 public:
     AstPrimitive(FileLine* fl, const string& name)
         : ASTGEN_SUPER_Primitive(fl, name) {}
-    ASTGEN_MEMBERS_Primitive;
+    ASTGEN_MEMBERS_AstPrimitive;
     string verilogKwd() const override { return "primitive"; }
     bool timescaleMatters() const override { return false; }
 };
@@ -2572,7 +2595,7 @@ public:
         UASSERT_OBJ(!v3Global.assertDTypesResolved(), this,
                     "not coded to create after dtypes resolved");
     }
-    ASTGEN_MEMBERS_SelBit;
+    ASTGEN_MEMBERS_AstSelBit;
     AstNode* bitp() const { return rhsp(); }
 };
 class AstSelExtract final : public AstNodePreSel {
@@ -2580,7 +2603,7 @@ class AstSelExtract final : public AstNodePreSel {
 public:
     AstSelExtract(FileLine* fl, AstNode* fromp, AstNode* msbp, AstNode* lsbp)
         : ASTGEN_SUPER_SelExtract(fl, fromp, msbp, lsbp) {}
-    ASTGEN_MEMBERS_SelExtract;
+    ASTGEN_MEMBERS_AstSelExtract;
     AstNode* leftp() const { return rhsp(); }
     AstNode* rightp() const { return thsp(); }
 };
@@ -2590,7 +2613,7 @@ class AstSelMinus final : public AstNodePreSel {
 public:
     AstSelMinus(FileLine* fl, AstNode* fromp, AstNode* bitp, AstNode* widthp)
         : ASTGEN_SUPER_SelMinus(fl, fromp, bitp, widthp) {}
-    ASTGEN_MEMBERS_SelMinus;
+    ASTGEN_MEMBERS_AstSelMinus;
     AstNode* bitp() const { return rhsp(); }
     AstNode* widthp() const { return thsp(); }
 };
@@ -2600,7 +2623,7 @@ class AstSelPlus final : public AstNodePreSel {
 public:
     AstSelPlus(FileLine* fl, AstNode* fromp, AstNode* bitp, AstNode* widthp)
         : ASTGEN_SUPER_SelPlus(fl, fromp, bitp, widthp) {}
-    ASTGEN_MEMBERS_SelPlus;
+    ASTGEN_MEMBERS_AstSelPlus;
     AstNode* bitp() const { return rhsp(); }
     AstNode* widthp() const { return thsp(); }
 };
@@ -2616,7 +2639,7 @@ public:
         , m_keyword{keyword} {
         this->sensesp(sensesp);
     }
-    ASTGEN_MEMBERS_Always;
+    ASTGEN_MEMBERS_AstAlways;
     //
     void dump(std::ostream& str) const override;
     VAlwaysKwd keyword() const { return m_keyword; }
@@ -2629,27 +2652,27 @@ public:
         : ASTGEN_SUPER_AlwaysPost(fl, stmtsp) {
         this->sensesp(sensesp);
     }
-    ASTGEN_MEMBERS_AlwaysPost;
+    ASTGEN_MEMBERS_AstAlwaysPost;
 };
 class AstAlwaysPostponed final : public AstNodeProcedure {
-    // Like always but postponement scheduling region
+    // Like always but Postponed scheduling region
 
 public:
     AstAlwaysPostponed(FileLine* fl, AstNode* stmtsp)
         : ASTGEN_SUPER_AlwaysPostponed(fl, stmtsp) {}
-    ASTGEN_MEMBERS_AlwaysPostponed;
+    ASTGEN_MEMBERS_AstAlwaysPostponed;
 };
 class AstFinal final : public AstNodeProcedure {
 public:
     AstFinal(FileLine* fl, AstNode* stmtsp)
         : ASTGEN_SUPER_Final(fl, stmtsp) {}
-    ASTGEN_MEMBERS_Final;
+    ASTGEN_MEMBERS_AstFinal;
 };
 class AstInitial final : public AstNodeProcedure {
 public:
     AstInitial(FileLine* fl, AstNode* stmtsp)
         : ASTGEN_SUPER_Initial(fl, stmtsp) {}
-    ASTGEN_MEMBERS_Initial;
+    ASTGEN_MEMBERS_AstInitial;
 };
 class AstInitialAutomatic final : public AstNodeProcedure {
     // Automatic variable initialization
@@ -2657,7 +2680,7 @@ class AstInitialAutomatic final : public AstNodeProcedure {
 public:
     AstInitialAutomatic(FileLine* fl, AstNode* stmtsp)
         : ASTGEN_SUPER_InitialAutomatic(fl, stmtsp) {}
-    ASTGEN_MEMBERS_InitialAutomatic;
+    ASTGEN_MEMBERS_AstInitialAutomatic;
 };
 class AstInitialStatic final : public AstNodeProcedure {
     // Static variable initialization
@@ -2665,7 +2688,7 @@ class AstInitialStatic final : public AstNodeProcedure {
 public:
     AstInitialStatic(FileLine* fl, AstNode* stmtsp)
         : ASTGEN_SUPER_InitialStatic(fl, stmtsp) {}
-    ASTGEN_MEMBERS_InitialStatic;
+    ASTGEN_MEMBERS_AstInitialStatic;
 };
 
 // === AstNodeRange ===
@@ -2678,7 +2701,7 @@ public:
         : ASTGEN_SUPER_BracketRange(fl) {
         this->elementsp(elementsp);
     }
-    ASTGEN_MEMBERS_BracketRange;
+    ASTGEN_MEMBERS_AstBracketRange;
     virtual string emitC() { V3ERROR_NA_RETURN(""); }
     virtual string emitVerilog() { V3ERROR_NA_RETURN(""); }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -2698,20 +2721,20 @@ public:
     }
     inline AstRange(FileLine* fl, int left, int right);
     inline AstRange(FileLine* fl, const VNumRange& range);
-    ASTGEN_MEMBERS_Range;
-    inline int leftConst() const;
-    inline int rightConst() const;
-    int hiConst() const {
+    ASTGEN_MEMBERS_AstRange;
+    inline int leftConst() const VL_MT_SAFE;
+    inline int rightConst() const VL_MT_SAFE;
+    int hiConst() const VL_MT_SAFE {
         const int l = leftConst();
         const int r = rightConst();
         return l > r ? l : r;
     }
-    int loConst() const {
+    int loConst() const VL_MT_SAFE {
         const int l = leftConst();
         const int r = rightConst();
         return l > r ? r : l;
     }
-    int elementsConst() const { return hiConst() - loConst() + 1; }
+    int elementsConst() const VL_MT_SAFE { return hiConst() - loConst() + 1; }
     bool littleEndian() const { return leftConst() < rightConst(); }
     void dump(std::ostream& str) const override;
     virtual string emitC() { V3ERROR_NA_RETURN(""); }
@@ -2722,7 +2745,7 @@ class AstUnsizedRange final : public AstNodeRange {
 public:
     explicit AstUnsizedRange(FileLine* fl)
         : ASTGEN_SUPER_UnsizedRange(fl) {}
-    ASTGEN_MEMBERS_UnsizedRange;
+    ASTGEN_MEMBERS_AstUnsizedRange;
     virtual string emitC() { V3ERROR_NA_RETURN(""); }
     virtual string emitVerilog() { return "[]"; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -2732,7 +2755,7 @@ class AstWildcardRange final : public AstNodeRange {
 public:
     explicit AstWildcardRange(FileLine* fl)
         : ASTGEN_SUPER_WildcardRange(fl) {}
-    ASTGEN_MEMBERS_WildcardRange;
+    ASTGEN_MEMBERS_AstWildcardRange;
     virtual string emitC() { V3ERROR_NA_RETURN(""); }
     virtual string emitVerilog() { return "[*]"; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -2750,7 +2773,7 @@ public:
         addSensesp(sensesp);
         addStmtsp(stmtsp);
     }
-    ASTGEN_MEMBERS_AlwaysPublic;
+    ASTGEN_MEMBERS_AstAlwaysPublic;
     bool same(const AstNode* /*samep*/) const override { return true; }
     // Special accessors
     bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); }
@@ -2760,12 +2783,35 @@ class AstBreak final : public AstNodeStmt {
 public:
     explicit AstBreak(FileLine* fl)
         : ASTGEN_SUPER_Break(fl) {}
-    ASTGEN_MEMBERS_Break;
+    ASTGEN_MEMBERS_AstBreak;
     string verilogKwd() const override { return "break"; }
     bool isBrancher() const override {
         return true;  // SPECIAL: We don't process code after breaks
     }
 };
+class AstCAwait final : public AstNodeStmt {
+    // Emit C++'s co_await statement
+    // @astgen op1 := exprp : AstNode
+    AstSenTree* m_sensesp;  // Sentree related to this await
+public:
+    AstCAwait(FileLine* fl, AstNode* exprp, AstSenTree* sensesp = nullptr)
+        : ASTGEN_SUPER_CAwait(fl)
+        , m_sensesp{sensesp} {
+        this->exprp(exprp);
+    }
+    ASTGEN_MEMBERS_AstCAwait;
+    bool isTimingControl() const override { return true; }
+    const char* broken() const override {
+        BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists());
+        return nullptr;
+    }
+    void cloneRelink() override {
+        if (m_sensesp && m_sensesp->clonep()) m_sensesp = m_sensesp->clonep();
+    }
+    void dump(std::ostream& str) const override;
+    AstSenTree* sensesp() const { return m_sensesp; }
+    void clearSensesp() { m_sensesp = nullptr; }
+};
 class AstCMethodHard final : public AstNodeStmt {
     // A reference to a "C" hardcoded member task (or function)
     // PARENTS: stmt/math
@@ -2791,7 +2837,7 @@ public:
         this->fromp(fromp);
         this->addPinsp(pinsp);
     }
-    ASTGEN_MEMBERS_CMethodHard;
+    ASTGEN_MEMBERS_AstCMethodHard;
     string name() const override { return m_name; }  // * = Var name
     bool hasDType() const override { return true; }
     void name(const string& name) override { m_name = name; }
@@ -2805,6 +2851,7 @@ public:
         statement(true);
         dtypeSetVoid();
     }
+    int instrCount() const override;
 };
 class AstCReset final : public AstNodeStmt {
     // Reset variable at startup
@@ -2814,7 +2861,7 @@ public:
         : ASTGEN_SUPER_CReset(fl) {
         this->varrefp(varrefp);
     }
-    ASTGEN_MEMBERS_CReset;
+    ASTGEN_MEMBERS_AstCReset;
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -2827,7 +2874,7 @@ public:
         : ASTGEN_SUPER_CReturn(fl) {
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_CReturn;
+    ASTGEN_MEMBERS_AstCReturn;
     int instrCount() const override { return widthInstrs(); }
     bool same(const AstNode* /*samep*/) const override { return true; }
 };
@@ -2840,28 +2887,11 @@ public:
         this->addExprsp(exprsp);
     }
     inline AstCStmt(FileLine* fl, const string& textStmt);
-    ASTGEN_MEMBERS_CStmt;
+    ASTGEN_MEMBERS_AstCStmt;
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
     bool same(const AstNode* /*samep*/) const override { return true; }
 };
-class AstChangeDet final : public AstNodeStmt {
-    // A comparison to determine change detection, common & must be fast.
-    // @astgen op1 := lhsp : Optional[AstNode]
-    // @astgen op2 := rhsp : Optional[AstNode]
-public:
-    // Null lhs+rhs used to indicate change needed with no spec vars
-    AstChangeDet(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
-        : ASTGEN_SUPER_ChangeDet(fl) {
-        this->lhsp(lhsp);
-        this->rhsp(rhsp);
-    }
-    ASTGEN_MEMBERS_ChangeDet;
-    bool isGateOptimizable() const override { return false; }
-    bool isPredictOptimizable() const override { return false; }
-    int instrCount() const override { return widthInstrs() * 2; }  // xor, or/logor
-    bool same(const AstNode* /*samep*/) const override { return true; }
-};
 class AstComment final : public AstNodeStmt {
     // Some comment to put into the output stream
     // Parents:  {statement list}
@@ -2872,7 +2902,7 @@ public:
         : ASTGEN_SUPER_Comment(fl)
         , m_showAt{showAt}
         , m_name{name} {}
-    ASTGEN_MEMBERS_Comment;
+    ASTGEN_MEMBERS_AstComment;
     string name() const override { return m_name; }  // * = Text
     bool same(const AstNode* samep) const override { return true; }  // Ignore name in comments
     virtual bool showAt() const { return m_showAt; }
@@ -2881,7 +2911,7 @@ class AstContinue final : public AstNodeStmt {
 public:
     explicit AstContinue(FileLine* fl)
         : ASTGEN_SUPER_Continue(fl) {}
-    ASTGEN_MEMBERS_Continue;
+    ASTGEN_MEMBERS_AstContinue;
     string verilogKwd() const override { return "continue"; }
     bool isBrancher() const override {
         return true;  // SPECIAL: We don't process code after breaks
@@ -2908,7 +2938,7 @@ public:
         , m_text{comment}
         , m_linescov{linescov}
         , m_offset{offset} {}
-    ASTGEN_MEMBERS_CoverDecl;
+    ASTGEN_MEMBERS_AstCoverDecl;
     const char* broken() const override {
         BROKEN_RTN(m_dataDeclp && !m_dataDeclp->brokeExists());
         if (m_dataDeclp && m_dataDeclp->m_dataDeclp) {  // Avoid O(n^2) accessing
@@ -2953,7 +2983,7 @@ public:
     AstCoverInc(FileLine* fl, AstCoverDecl* declp)
         : ASTGEN_SUPER_CoverInc(fl)
         , m_declp{declp} {}
-    ASTGEN_MEMBERS_CoverInc;
+    ASTGEN_MEMBERS_AstCoverInc;
     const char* broken() const override {
         BROKEN_RTN(!declp()->brokeExists());
         return nullptr;
@@ -2985,7 +3015,7 @@ public:
         this->origp(origp);
         this->changep(changep);
     }
-    ASTGEN_MEMBERS_CoverToggle;
+    ASTGEN_MEMBERS_AstCoverToggle;
     int instrCount() const override { return 3 + INSTR_COUNT_BRANCH + INSTR_COUNT_LD; }
     bool same(const AstNode* /*samep*/) const override { return true; }
     bool isGateOptimizable() const override { return false; }
@@ -3000,12 +3030,12 @@ class AstDelay final : public AstNodeStmt {
     // @astgen op1 := lhsp : AstNode // Delay value
     // @astgen op2 := stmtsp : List[AstNode] // Statements under delay
 public:
-    AstDelay(FileLine* fl, AstNode* lhsp, AstNode* stmtsp)
+    AstDelay(FileLine* fl, AstNode* lhsp)
         : ASTGEN_SUPER_Delay(fl) {
         this->lhsp(lhsp);
-        this->addStmtsp(stmtsp);
     }
-    ASTGEN_MEMBERS_Delay;
+    ASTGEN_MEMBERS_AstDelay;
+    bool isTimingControl() const override { return true; }
     bool same(const AstNode* /*samep*/) const override { return true; }
 };
 class AstDisable final : public AstNodeStmt {
@@ -3015,7 +3045,7 @@ public:
     AstDisable(FileLine* fl, const string& name)
         : ASTGEN_SUPER_Disable(fl)
         , m_name{name} {}
-    ASTGEN_MEMBERS_Disable;
+    ASTGEN_MEMBERS_AstDisable;
     string name() const override { return m_name; }  // * = Block name
     void name(const string& flag) override { m_name = flag; }
     bool isBrancher() const override {
@@ -3027,7 +3057,7 @@ class AstDisableFork final : public AstNodeStmt {
 public:
     explicit AstDisableFork(FileLine* fl)
         : ASTGEN_SUPER_DisableFork(fl) {}
-    ASTGEN_MEMBERS_DisableFork;
+    ASTGEN_MEMBERS_AstDisableFork;
 };
 class AstDisplay final : public AstNodeStmt {
     // Parents: stmtlist
@@ -3051,7 +3081,7 @@ public:
         this->fmtp(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp, missingArgChar});
         this->filep(filep);
     }
-    ASTGEN_MEMBERS_Display;
+    ASTGEN_MEMBERS_AstDisplay;
     void dump(std::ostream& str) const override;
     const char* broken() const override {
         BROKEN_RTN(!fmtp());
@@ -3075,14 +3105,6 @@ public:
     // * = Add a newline for $display
     bool addNewline() const { return displayType().addNewline(); }
 };
-class AstDpiExportUpdated final : public AstNodeStmt {
-    // Denotes that the referenced variable may have been updated via a DPI Export
-    // @astgen op1 := varRefp : AstVarRef
-public:
-    inline AstDpiExportUpdated(FileLine* fl, AstVarScope* varScopep);
-    ASTGEN_MEMBERS_DpiExportUpdated;
-    inline AstVarScope* varScopep() const;
-};
 class AstDumpCtl final : public AstNodeStmt {
     // $dumpon etc
     // Parents: expr
@@ -3094,7 +3116,7 @@ public:
         , m_ctlType{ctlType} {
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_DumpCtl;
+    ASTGEN_MEMBERS_AstDumpCtl;
     string verilogKwd() const override { return ctlType().ascii(); }
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
@@ -3113,12 +3135,9 @@ public:
         this->sensesp(sensesp);
         this->addStmtsp(stmtsp);
     }
-    ASTGEN_MEMBERS_EventControl;
+    ASTGEN_MEMBERS_AstEventControl;
     string verilogKwd() const override { return "@(%l) %r"; }
-    bool isGateOptimizable() const override { return false; }
-    bool isPredictOptimizable() const override { return false; }
-    bool isPure() const override { return false; }
-    bool isOutputter() const override { return false; }
+    bool isTimingControl() const override { return true; }
     int instrCount() const override { return 0; }
 };
 class AstFClose final : public AstNodeStmt {
@@ -3129,7 +3148,7 @@ public:
         : ASTGEN_SUPER_FClose(fl) {
         this->filep(filep);
     }
-    ASTGEN_MEMBERS_FClose;
+    ASTGEN_MEMBERS_AstFClose;
     string verilogKwd() const override { return "$fclose"; }
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
@@ -3146,7 +3165,7 @@ public:
         : ASTGEN_SUPER_FFlush(fl) {
         this->filep(filep);
     }
-    ASTGEN_MEMBERS_FFlush;
+    ASTGEN_MEMBERS_AstFFlush;
     string verilogKwd() const override { return "$fflush"; }
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
@@ -3167,7 +3186,7 @@ public:
         this->filenamep(filenamep);
         this->modep(modep);
     }
-    ASTGEN_MEMBERS_FOpen;
+    ASTGEN_MEMBERS_AstFOpen;
     string verilogKwd() const override { return "$fopen"; }
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
@@ -3186,7 +3205,7 @@ public:
         this->filep(filep);
         this->filenamep(filenamep);
     }
-    ASTGEN_MEMBERS_FOpenMcd;
+    ASTGEN_MEMBERS_AstFOpenMcd;
     string verilogKwd() const override { return "$fopen"; }
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
@@ -3199,7 +3218,7 @@ class AstFinish final : public AstNodeStmt {
 public:
     explicit AstFinish(FileLine* fl)
         : ASTGEN_SUPER_Finish(fl) {}
-    ASTGEN_MEMBERS_Finish;
+    ASTGEN_MEMBERS_AstFinish;
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
     bool isPure() const override { return false; }  // SPECIAL: $display has 'visual' ordering
@@ -3208,6 +3227,19 @@ public:
     int instrCount() const override { return 0; }  // Rarely executes
     bool same(const AstNode* samep) const override { return fileline() == samep->fileline(); }
 };
+class AstFireEvent final : public AstNodeStmt {
+    // '-> _' and '->> _' event trigger statements
+    // @astgen op1 := operandp : AstNode
+    const bool m_delayed;  // Delayed (->>) vs non-delayed (->)
+public:
+    AstFireEvent(FileLine* fl, AstNode* operandp, bool delayed)
+        : ASTGEN_SUPER_FireEvent(fl)
+        , m_delayed{delayed} {
+        this->operandp(operandp);
+    }
+    ASTGEN_MEMBERS_AstFireEvent;
+    bool isDelayed() const { return m_delayed; }
+};
 class AstForeach final : public AstNodeStmt {
     // @astgen op1 := arrayp : AstNode
     // @astgen op2 := stmtsp : List[AstNode]
@@ -3217,7 +3249,7 @@ public:
         this->arrayp(arrayp);
         this->addStmtsp(stmtsp);
     }
-    ASTGEN_MEMBERS_Foreach;
+    ASTGEN_MEMBERS_AstForeach;
     bool isGateOptimizable() const override { return false; }
     int instrCount() const override { return INSTR_COUNT_BRANCH; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -3240,7 +3272,7 @@ public:
     }
     const char* broken() const override;
     void cloneRelink() override;
-    ASTGEN_MEMBERS_JumpBlock;
+    ASTGEN_MEMBERS_AstJumpBlock;
     int instrCount() const override { return 0; }
     bool maybePointedTo() const override { return true; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -3260,7 +3292,7 @@ public:
     AstJumpGo(FileLine* fl, AstJumpLabel* labelp)
         : ASTGEN_SUPER_JumpGo(fl)
         , m_labelp{labelp} {}
-    ASTGEN_MEMBERS_JumpGo;
+    ASTGEN_MEMBERS_AstJumpGo;
     const char* broken() const override;
     void cloneRelink() override;
     void dump(std::ostream& str) const override;
@@ -3284,7 +3316,7 @@ public:
     AstJumpLabel(FileLine* fl, AstJumpBlock* blockp)
         : ASTGEN_SUPER_JumpLabel(fl)
         , m_blockp{blockp} {}
-    ASTGEN_MEMBERS_JumpLabel;
+    ASTGEN_MEMBERS_AstJumpLabel;
     bool maybePointedTo() const override { return true; }
     const char* broken() const override {
         BROKEN_RTN(!blockp()->brokeExistsAbove());
@@ -3308,7 +3340,7 @@ public:
     AstMonitorOff(FileLine* fl, bool off)
         : ASTGEN_SUPER_MonitorOff(fl)
         , m_off{off} {}
-    ASTGEN_MEMBERS_MonitorOff;
+    ASTGEN_MEMBERS_AstMonitorOff;
     string verilogKwd() const override { return m_off ? "$monitoroff" : "$monitoron"; }
     bool isGateOptimizable() const override { return false; }  // Though deleted before opt
     bool isPredictOptimizable() const override { return false; }  // Though deleted before opt
@@ -3327,7 +3359,7 @@ class AstPrintTimeScale final : public AstNodeStmt {
 public:
     explicit AstPrintTimeScale(FileLine* fl)
         : ASTGEN_SUPER_PrintTimeScale(fl) {}
-    ASTGEN_MEMBERS_PrintTimeScale;
+    ASTGEN_MEMBERS_AstPrintTimeScale;
     void name(const string& name) override { m_name = name; }
     string name() const override { return m_name; }  // * = Var name
     void dump(std::ostream& str) const override;
@@ -3348,7 +3380,7 @@ public:
         : ASTGEN_SUPER_Release(fl) {
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_Release;
+    ASTGEN_MEMBERS_AstRelease;
 };
 class AstRepeat final : public AstNodeStmt {
     // @astgen op1 := countp : AstNode
@@ -3359,7 +3391,7 @@ public:
         this->countp(countp);
         this->addStmtsp(stmtsp);
     }
-    ASTGEN_MEMBERS_Repeat;
+    ASTGEN_MEMBERS_AstRepeat;
     bool isGateOptimizable() const override { return false; }  // Not relevant - converted to FOR
     int instrCount() const override { return INSTR_COUNT_BRANCH; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -3372,7 +3404,7 @@ public:
         : ASTGEN_SUPER_Return(fl) {
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_Return;
+    ASTGEN_MEMBERS_AstReturn;
     string verilogKwd() const override { return "return"; }
     bool isBrancher() const override {
         return true;  // SPECIAL: We don't process code after breaks
@@ -3394,7 +3426,7 @@ public:
         this->fmtp(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp, missingArgChar});
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_SFormat;
+    ASTGEN_MEMBERS_AstSFormat;
     const char* broken() const override {
         BROKEN_RTN(!fmtp());
         return nullptr;
@@ -3412,7 +3444,7 @@ class AstStop final : public AstNodeStmt {
 public:
     AstStop(FileLine* fl, bool maybe)
         : ASTGEN_SUPER_Stop(fl) {}
-    ASTGEN_MEMBERS_Stop;
+    ASTGEN_MEMBERS_AstStop;
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
     bool isPure() const override { return false; }  // SPECIAL: $display has 'visual' ordering
@@ -3429,7 +3461,7 @@ public:
         : ASTGEN_SUPER_SysFuncAsTask(fl) {
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_SysFuncAsTask;
+    ASTGEN_MEMBERS_AstSysFuncAsTask;
     string verilogKwd() const override { return ""; }
     bool isGateOptimizable() const override { return true; }
     bool isPredictOptimizable() const override { return true; }
@@ -3445,7 +3477,7 @@ public:
         : ASTGEN_SUPER_SysIgnore(fl) {
         this->addExprsp(exprsp);
     }
-    ASTGEN_MEMBERS_SysIgnore;
+    ASTGEN_MEMBERS_AstSysIgnore;
     string verilogKwd() const override { return "$ignored"; }
     bool isGateOptimizable() const override { return false; }  // Though deleted before opt
     bool isPredictOptimizable() const override { return false; }  // Though deleted before opt
@@ -3461,7 +3493,7 @@ public:
         : ASTGEN_SUPER_SystemT(fl) {
         this->lhsp(lhsp);
     }
-    ASTGEN_MEMBERS_SystemT;
+    ASTGEN_MEMBERS_AstSystemT;
     string verilogKwd() const override { return "$system"; }
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
@@ -3485,7 +3517,7 @@ public:
         this->suffixp(suffixp);
         this->widthp(widthp);
     }
-    ASTGEN_MEMBERS_TimeFormat;
+    ASTGEN_MEMBERS_AstTimeFormat;
     string verilogKwd() const override { return "$timeformat"; }
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
@@ -3527,7 +3559,7 @@ public:
     }
     void dump(std::ostream& str) const override;
     int instrCount() const override { return 100; }  // Large...
-    ASTGEN_MEMBERS_TraceDecl;
+    ASTGEN_MEMBERS_AstTraceDecl;
     string name() const override { return m_showname; }
     bool maybePointedTo() const override { return true; }
     bool hasDType() const override { return true; }
@@ -3563,7 +3595,7 @@ public:
         this->valuep(
             declp->valuep()->cloneTree(true));  // TODO: maybe use reference to TraceDecl instead?
     }
-    ASTGEN_MEMBERS_TraceInc;
+    ASTGEN_MEMBERS_AstTraceInc;
     const char* broken() const override {
         BROKEN_RTN(!declp()->brokeExists());
         return nullptr;
@@ -3591,7 +3623,7 @@ public:
     AstTracePopNamePrefix(FileLine* fl, unsigned count)
         : ASTGEN_SUPER_TracePopNamePrefix(fl)
         , m_count{count} {}
-    ASTGEN_MEMBERS_TracePopNamePrefix;
+    ASTGEN_MEMBERS_AstTracePopNamePrefix;
     bool same(const AstNode* samep) const override { return false; }
     unsigned count() const { return m_count; }
 };
@@ -3601,7 +3633,7 @@ public:
     AstTracePushNamePrefix(FileLine* fl, const string& prefix)
         : ASTGEN_SUPER_TracePushNamePrefix(fl)
         , m_prefix{prefix} {}
-    ASTGEN_MEMBERS_TracePushNamePrefix;
+    ASTGEN_MEMBERS_AstTracePushNamePrefix;
     bool same(const AstNode* samep) const override { return false; }
     string prefix() const { return m_prefix; }
 };
@@ -3613,7 +3645,7 @@ public:
         : ASTGEN_SUPER_UCStmt(fl) {
         this->addExprsp(exprsp);
     }
-    ASTGEN_MEMBERS_UCStmt;
+    ASTGEN_MEMBERS_AstUCStmt;
     bool isGateOptimizable() const override { return false; }
     bool isPredictOptimizable() const override { return false; }
     bool isPure() const override { return false; }
@@ -3629,15 +3661,16 @@ public:
         this->condp(condp);
         this->addStmtsp(stmtsp);
     }
-    ASTGEN_MEMBERS_Wait;
+    ASTGEN_MEMBERS_AstWait;
     bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); }
+    bool isTimingControl() const override { return true; }
 };
 class AstWaitFork final : public AstNodeStmt {
     // A "wait fork" statement
 public:
     explicit AstWaitFork(FileLine* fl)
         : ASTGEN_SUPER_WaitFork(fl) {}
-    ASTGEN_MEMBERS_WaitFork;
+    ASTGEN_MEMBERS_AstWaitFork;
 };
 class AstWhile final : public AstNodeStmt {
     // @astgen op1 := precondsp : List[AstNode]
@@ -3651,7 +3684,7 @@ public:
         this->addStmtsp(stmtsp);
         this->addIncsp(incsp);
     }
-    ASTGEN_MEMBERS_While;
+    ASTGEN_MEMBERS_AstWhile;
     bool isGateOptimizable() const override { return false; }
     int instrCount() const override { return INSTR_COUNT_BRANCH; }
     bool same(const AstNode* /*samep*/) const override { return true; }
@@ -3679,7 +3712,7 @@ public:
         this->valueArgRefp(valueArgRefp);
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_With;
+    ASTGEN_MEMBERS_AstWith;
     bool same(const AstNode* /*samep*/) const override { return true; }
     bool hasDType() const override { return true; }
     const char* broken() const override {
@@ -3702,7 +3735,7 @@ public:
         this->funcrefp(funcrefp);
         this->exprp(exprp);
     }
-    ASTGEN_MEMBERS_WithParse;
+    ASTGEN_MEMBERS_AstWithParse;
     bool same(const AstNode* /*samep*/) const override { return true; }
 };
 
@@ -3713,9 +3746,10 @@ public:
         : ASTGEN_SUPER_Assign(fl, lhsp, rhsp, timingControlp) {
         dtypeFrom(lhsp);
     }
-    ASTGEN_MEMBERS_Assign;
+    ASTGEN_MEMBERS_AstAssign;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
-        return new AstAssign(this->fileline(), lhsp, rhsp);
+        AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr;
+        return new AstAssign{fileline(), lhsp, rhsp, controlp};
     }
     bool brokeLhsMustBeLvalue() const override { return true; }
 };
@@ -3725,7 +3759,7 @@ class AstAssignAlias final : public AstNodeAssign {
 public:
     AstAssignAlias(FileLine* fl, AstVarRef* lhsp, AstVarRef* rhsp)
         : ASTGEN_SUPER_AssignAlias(fl, (AstNode*)lhsp, (AstNode*)rhsp) {}
-    ASTGEN_MEMBERS_AssignAlias;
+    ASTGEN_MEMBERS_AstAssignAlias;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { V3ERROR_NA_RETURN(nullptr); }
     bool brokeLhsMustBeLvalue() const override { return false; }
 };
@@ -3733,9 +3767,10 @@ class AstAssignDly final : public AstNodeAssign {
 public:
     AstAssignDly(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr)
         : ASTGEN_SUPER_AssignDly(fl, lhsp, rhsp, timingControlp) {}
-    ASTGEN_MEMBERS_AssignDly;
+    ASTGEN_MEMBERS_AstAssignDly;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
-        return new AstAssignDly(this->fileline(), lhsp, rhsp);
+        AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr;
+        return new AstAssignDly{fileline(), lhsp, rhsp, controlp};
     }
     bool isGateOptimizable() const override { return false; }
     string verilogKwd() const override { return "<="; }
@@ -3746,7 +3781,7 @@ class AstAssignForce final : public AstNodeAssign {
 public:
     AstAssignForce(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_AssignForce(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_AssignForce;
+    ASTGEN_MEMBERS_AstAssignForce;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAssignForce{this->fileline(), lhsp, rhsp};
     }
@@ -3757,7 +3792,7 @@ class AstAssignPost final : public AstNodeAssign {
 public:
     AstAssignPost(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_AssignPost(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_AssignPost;
+    ASTGEN_MEMBERS_AstAssignPost;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAssignPost(this->fileline(), lhsp, rhsp);
     }
@@ -3768,7 +3803,7 @@ class AstAssignPre final : public AstNodeAssign {
 public:
     AstAssignPre(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
         : ASTGEN_SUPER_AssignPre(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_AssignPre;
+    ASTGEN_MEMBERS_AstAssignPre;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAssignPre(this->fileline(), lhsp, rhsp);
     }
@@ -3781,7 +3816,7 @@ public:
         : ASTGEN_SUPER_AssignVarScope(fl, lhsp, rhsp) {
         dtypeFrom(rhsp);
     }
-    ASTGEN_MEMBERS_AssignVarScope;
+    ASTGEN_MEMBERS_AstAssignVarScope;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
         return new AstAssignVarScope(this->fileline(), lhsp, rhsp);
     }
@@ -3791,21 +3826,20 @@ class AstAssignW final : public AstNodeAssign {
     // Like assign, but wire/assign's in verilog, the only setting of the specified variable
     // @astgen op4 := strengthSpecp : Optional[AstStrengthSpec]
 public:
-    AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
-        : ASTGEN_SUPER_AssignW(fl, lhsp, rhsp) {}
-    ASTGEN_MEMBERS_AssignW;
+    AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr)
+        : ASTGEN_SUPER_AssignW(fl, lhsp, rhsp, timingControlp) {}
+    ASTGEN_MEMBERS_AstAssignW;
     AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
-        return new AstAssignW(this->fileline(), lhsp, rhsp);
+        AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr;
+        return new AstAssignW{fileline(), lhsp, rhsp, controlp};
+    }
+    bool isTimingControl() const override {
+        return timingControlp() || lhsp()->exists([](const AstNodeVarRef* refp) {
+            return refp->access().isWriteOrRW() && refp->varp()->delayp();
+        });
     }
     bool brokeLhsMustBeLvalue() const override { return true; }
-    AstAlways* convertToAlways() {
-        AstNode* const lhs1p = lhsp()->unlinkFrBack();
-        AstNode* const rhs1p = rhsp()->unlinkFrBack();
-        AstAlways* const newp = new AstAlways(fileline(), VAlwaysKwd::ALWAYS, nullptr,
-                                              new AstAssign(fileline(), lhs1p, rhs1p));
-        replaceWith(newp);  // User expected to then deleteTree();
-        return newp;
-    }
+    AstAlways* convertToAlways();
 };
 
 // === AstNodeCCall ===
@@ -3819,7 +3853,7 @@ class AstCCall final : public AstNodeCCall {
 public:
     AstCCall(FileLine* fl, AstCFunc* funcp, AstNode* argsp = nullptr)
         : ASTGEN_SUPER_CCall(fl, funcp, argsp) {}
-    ASTGEN_MEMBERS_CCall;
+    ASTGEN_MEMBERS_AstCCall;
 
     string selfPointer() const { return m_selfPointer; }
     void selfPointer(const string& value) { m_selfPointer = value; }
@@ -3834,7 +3868,7 @@ public:
         : ASTGEN_SUPER_CMethodCall(fl, funcp, argsp) {
         this->fromp(fromp);
     }
-    ASTGEN_MEMBERS_CMethodCall;
+    ASTGEN_MEMBERS_AstCMethodCall;
     const char* broken() const override {
         BROKEN_BASE_RTN(AstNodeCCall::broken());
         BROKEN_RTN(!fromp());
@@ -3851,7 +3885,7 @@ public:
         statement(false);
     }
     bool hasDType() const override { return true; }
-    ASTGEN_MEMBERS_CNew;
+    ASTGEN_MEMBERS_AstCNew;
 };
 
 // === AstNodeCase ===
@@ -3869,7 +3903,7 @@ public:
     AstCase(FileLine* fl, VCaseType casex, AstNode* exprp, AstCaseItem* itemsp)
         : ASTGEN_SUPER_Case(fl, exprp, itemsp)
         , m_casex{casex} {}
-    ASTGEN_MEMBERS_Case;
+    ASTGEN_MEMBERS_AstCase;
     string verilogKwd() const override { return casez() ? "casez" : casex() ? "casex" : "case"; }
     bool same(const AstNode* samep) const override {
         return m_casex == static_cast(samep)->m_casex;
@@ -3896,14 +3930,14 @@ class AstGenCase final : public AstNodeCase {
 public:
     AstGenCase(FileLine* fl, AstNode* exprp, AstCaseItem* itemsp)
         : ASTGEN_SUPER_GenCase(fl, exprp, itemsp) {}
-    ASTGEN_MEMBERS_GenCase;
+    ASTGEN_MEMBERS_AstGenCase;
 };
 
 // === AstNodeCoverOrAssert ===
 class AstAssert final : public AstNodeCoverOrAssert {
     // @astgen op3 := failsp: List[AstNode] // Statments when propp is failing/falsey
 public:
-    ASTGEN_MEMBERS_Assert;
+    ASTGEN_MEMBERS_AstAssert;
     AstAssert(FileLine* fl, AstNode* propp, AstNode* passsp, AstNode* failsp, bool immediate,
               const string& name = "")
         : ASTGEN_SUPER_Assert(fl, propp, passsp, immediate, name) {
@@ -3914,7 +3948,7 @@ class AstAssertIntrinsic final : public AstNodeCoverOrAssert {
     // A $cast or other compiler inserted assert, that must run even without --assert option
     // @astgen op3 := failsp: List[AstNode] // Statments when propp is failing/falsey
 public:
-    ASTGEN_MEMBERS_AssertIntrinsic;
+    ASTGEN_MEMBERS_AstAssertIntrinsic;
     AstAssertIntrinsic(FileLine* fl, AstNode* propp, AstNode* passsp, AstNode* failsp,
                        bool immediate, const string& name = "")
         : ASTGEN_SUPER_AssertIntrinsic(fl, propp, passsp, immediate, name) {
@@ -3924,7 +3958,7 @@ public:
 class AstCover final : public AstNodeCoverOrAssert {
     // @astgen op3 := coverincsp: List[AstNode] // Coverage node
 public:
-    ASTGEN_MEMBERS_Cover;
+    ASTGEN_MEMBERS_AstCover;
     AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, bool immediate,
              const string& name = "")
         : ASTGEN_SUPER_Cover(fl, propp, stmtsp, immediate, name) {}
@@ -3932,7 +3966,7 @@ public:
 };
 class AstRestrict final : public AstNodeCoverOrAssert {
 public:
-    ASTGEN_MEMBERS_Restrict;
+    ASTGEN_MEMBERS_AstRestrict;
     AstRestrict(FileLine* fl, AstNode* propp)
         : ASTGEN_SUPER_Restrict(fl, propp, nullptr, false, "") {}
 };
@@ -3945,7 +3979,7 @@ public:
         : ASTGEN_SUPER_FuncRef(fl, false, namep, pinsp) {}
     AstFuncRef(FileLine* fl, const string& name, AstNode* pinsp)
         : ASTGEN_SUPER_FuncRef(fl, false, name, pinsp) {}
-    ASTGEN_MEMBERS_FuncRef;
+    ASTGEN_MEMBERS_AstFuncRef;
     bool hasDType() const override { return true; }
 };
 class AstMethodCall final : public AstNodeFTaskRef {
@@ -3966,7 +4000,7 @@ public:
         : ASTGEN_SUPER_MethodCall(fl, false, name, pinsp) {
         this->fromp(fromp);
     }
-    ASTGEN_MEMBERS_MethodCall;
+    ASTGEN_MEMBERS_AstMethodCall;
     const char* broken() const override {
         BROKEN_BASE_RTN(AstNodeFTaskRef::broken());
         BROKEN_RTN(!fromp());
@@ -3987,7 +4021,7 @@ class AstNew final : public AstNodeFTaskRef {
 public:
     AstNew(FileLine* fl, AstNode* pinsp)
         : ASTGEN_SUPER_New(fl, false, "new", pinsp) {}
-    ASTGEN_MEMBERS_New;
+    ASTGEN_MEMBERS_AstNew;
     virtual bool cleanOut() const { return true; }
     bool same(const AstNode* /*samep*/) const override { return true; }
     bool hasDType() const override { return true; }
@@ -4002,7 +4036,7 @@ public:
     }
     AstTaskRef(FileLine* fl, const string& name, AstNode* pinsp)
         : ASTGEN_SUPER_TaskRef(fl, true, name, pinsp) {}
-    ASTGEN_MEMBERS_TaskRef;
+    ASTGEN_MEMBERS_AstTaskRef;
 };
 
 // === AstNodeFor ===
@@ -4010,7 +4044,7 @@ class AstGenFor final : public AstNodeFor {
 public:
     AstGenFor(FileLine* fl, AstNode* initsp, AstNode* condp, AstNode* incsp, AstNode* stmtsp)
         : ASTGEN_SUPER_GenFor(fl, initsp, condp, incsp, stmtsp) {}
-    ASTGEN_MEMBERS_GenFor;
+    ASTGEN_MEMBERS_AstGenFor;
 };
 
 // === AstNodeIf ===
@@ -4018,7 +4052,7 @@ class AstGenIf final : public AstNodeIf {
 public:
     AstGenIf(FileLine* fl, AstNode* condp, AstNode* thensp, AstNode* elsesp)
         : ASTGEN_SUPER_GenIf(fl, condp, thensp, elsesp) {}
-    ASTGEN_MEMBERS_GenIf;
+    ASTGEN_MEMBERS_AstGenIf;
 };
 class AstIf final : public AstNodeIf {
 private:
@@ -4028,7 +4062,7 @@ private:
 public:
     AstIf(FileLine* fl, AstNode* condp, AstNode* thensp = nullptr, AstNode* elsesp = nullptr)
         : ASTGEN_SUPER_If(fl, condp, thensp, elsesp) {}
-    ASTGEN_MEMBERS_If;
+    ASTGEN_MEMBERS_AstIf;
     bool uniquePragma() const { return m_uniquePragma; }
     void uniquePragma(bool flag) { m_uniquePragma = flag; }
     bool unique0Pragma() const { return m_unique0Pragma; }
@@ -4043,7 +4077,7 @@ public:
     AstReadMem(FileLine* fl, bool hex, AstNode* filenamep, AstNode* memp, AstNode* lsbp,
                AstNode* msbp)
         : ASTGEN_SUPER_ReadMem(fl, hex, filenamep, memp, lsbp, msbp) {}
-    ASTGEN_MEMBERS_ReadMem;
+    ASTGEN_MEMBERS_AstReadMem;
     string verilogKwd() const override { return (isHex() ? "$readmemh" : "$readmemb"); }
     const char* cFuncPrefixp() const override { return "VL_READMEM_"; }
 };
@@ -4052,7 +4086,7 @@ public:
     AstWriteMem(FileLine* fl, bool hex, AstNode* filenamep, AstNode* memp, AstNode* lsbp,
                 AstNode* msbp)
         : ASTGEN_SUPER_WriteMem(fl, hex, filenamep, memp, lsbp, msbp) {}
-    ASTGEN_MEMBERS_WriteMem;
+    ASTGEN_MEMBERS_AstWriteMem;
     string verilogKwd() const override { return (isHex() ? "$writememh" : "$writememb"); }
     const char* cFuncPrefixp() const override { return "VL_WRITEMEM_"; }
 };
@@ -4062,7 +4096,7 @@ class AstScCtor final : public AstNodeText {
 public:
     AstScCtor(FileLine* fl, const string& textp)
         : ASTGEN_SUPER_ScCtor(fl, textp) {}
-    ASTGEN_MEMBERS_ScCtor;
+    ASTGEN_MEMBERS_AstScCtor;
     bool isPure() const override { return false; }  // SPECIAL: User may order w/other sigs
     bool isOutputter() const override { return true; }
 };
@@ -4070,7 +4104,7 @@ class AstScDtor final : public AstNodeText {
 public:
     AstScDtor(FileLine* fl, const string& textp)
         : ASTGEN_SUPER_ScDtor(fl, textp) {}
-    ASTGEN_MEMBERS_ScDtor;
+    ASTGEN_MEMBERS_AstScDtor;
     bool isPure() const override { return false; }  // SPECIAL: User may order w/other sigs
     bool isOutputter() const override { return true; }
 };
@@ -4078,7 +4112,7 @@ class AstScHdr final : public AstNodeText {
 public:
     AstScHdr(FileLine* fl, const string& textp)
         : ASTGEN_SUPER_ScHdr(fl, textp) {}
-    ASTGEN_MEMBERS_ScHdr;
+    ASTGEN_MEMBERS_AstScHdr;
     bool isPure() const override { return false; }  // SPECIAL: User may order w/other sigs
     bool isOutputter() const override { return true; }
 };
@@ -4086,7 +4120,7 @@ class AstScImp final : public AstNodeText {
 public:
     AstScImp(FileLine* fl, const string& textp)
         : ASTGEN_SUPER_ScImp(fl, textp) {}
-    ASTGEN_MEMBERS_ScImp;
+    ASTGEN_MEMBERS_AstScImp;
     bool isPure() const override { return false; }  // SPECIAL: User may order w/other sigs
     bool isOutputter() const override { return true; }
 };
@@ -4094,7 +4128,7 @@ class AstScImpHdr final : public AstNodeText {
 public:
     AstScImpHdr(FileLine* fl, const string& textp)
         : ASTGEN_SUPER_ScImpHdr(fl, textp) {}
-    ASTGEN_MEMBERS_ScImpHdr;
+    ASTGEN_MEMBERS_AstScImpHdr;
     bool isPure() const override { return false; }  // SPECIAL: User may order w/other sigs
     bool isOutputter() const override { return true; }
 };
@@ -4102,7 +4136,7 @@ class AstScInt final : public AstNodeText {
 public:
     AstScInt(FileLine* fl, const string& textp)
         : ASTGEN_SUPER_ScInt(fl, textp) {}
-    ASTGEN_MEMBERS_ScInt;
+    ASTGEN_MEMBERS_AstScInt;
     bool isPure() const override { return false; }  // SPECIAL: User may order w/other sigs
     bool isOutputter() const override { return true; }
 };
@@ -4112,7 +4146,7 @@ class AstText final : public AstNodeSimpleText {
 public:
     AstText(FileLine* fl, const string& textp, bool tracking = false)
         : ASTGEN_SUPER_Text(fl, textp, tracking) {}
-    ASTGEN_MEMBERS_Text;
+    ASTGEN_MEMBERS_AstText;
 };
 class AstTextBlock final : public AstNodeSimpleText {
     // @astgen op1 := nodesp : List[AstNode]
@@ -4122,7 +4156,7 @@ public:
                           bool commas = false)
         : ASTGEN_SUPER_TextBlock(fl, textp, tracking)
         , m_commas(commas) {}
-    ASTGEN_MEMBERS_TextBlock;
+    ASTGEN_MEMBERS_AstTextBlock;
     void commas(bool flag) { m_commas = flag; }
     bool commas() const { return m_commas; }
     void addText(FileLine* fl, const string& textp, bool tracking = false) {
diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp
index af7f7d74c..3487e1814 100644
--- a/src/V3AstNodes.cpp
+++ b/src/V3AstNodes.cpp
@@ -379,7 +379,7 @@ string AstVar::verilogKwd() const {
 }
 
 string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc,
-                         bool asRef) const {
+                         bool asRef) const VL_MT_SAFE {
     UASSERT_OBJ(!forReturn, this,
                 "Internal data is never passed as return, but as first argument");
     string ostatic;
@@ -679,7 +679,7 @@ class AstNodeDType::CTypeRecursed final {
 public:
     string m_type;  // The base type, e.g.: "Foo_t"s
     string m_dims;  // Array dimensions, e.g.: "[3][2][1]"
-    string render(const string& name, bool isRef) const {
+    string render(const string& name, bool isRef) const VL_MT_SAFE {
         string out;
         out += m_type;
         if (!name.empty()) out += " ";
@@ -696,7 +696,7 @@ public:
     }
 };
 
-string AstNodeDType::cType(const string& name, bool /*forFunc*/, bool isRef) const {
+string AstNodeDType::cType(const string& name, bool /*forFunc*/, bool isRef) const VL_MT_SAFE {
     const CTypeRecursed info = cTypeRecurse(false);
     return info.render(name, isRef);
 }
@@ -724,6 +724,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
         info.m_type += ">";
     } else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
         info.m_type = "VlClassRef<" + EmitCBaseVisitor::prefixNameProtect(adtypep) + ">";
+    } else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) {
+        info.m_type = EmitCBaseVisitor::prefixNameProtect(adtypep->ifaceViaCellp()) + "*";
     } else if (const auto* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
         if (adtypep->isCompound()) compound = true;
         const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(compound);
@@ -746,6 +748,18 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
             info.m_type = "std::string";
         } else if (bdtypep->keyword().isMTaskState()) {
             info.m_type = "VlMTaskVertex";
+        } else if (bdtypep->isTriggerVec()) {
+            info.m_type = "VlTriggerVec<" + cvtToStr(dtypep->width()) + ">";
+        } else if (bdtypep->isDelayScheduler()) {
+            info.m_type = "VlDelayScheduler";
+        } else if (bdtypep->isTriggerScheduler()) {
+            info.m_type = "VlTriggerScheduler";
+        } else if (bdtypep->isDynamicTriggerScheduler()) {
+            info.m_type = "VlDynamicTriggerScheduler";
+        } else if (bdtypep->isForkSync()) {
+            info.m_type = "VlForkSync";
+        } else if (bdtypep->isEvent()) {
+            info.m_type = "VlEvent";
         } else if (dtypep->widthMin() <= 8) {  // Handle unpacked arrays; not bdtypep->width
             info.m_type = "CData" + bitvec;
         } else if (dtypep->widthMin() <= 16) {
@@ -818,7 +832,7 @@ int AstNodeDType::widthPow2() const {
     return 1;
 }
 
-bool AstNodeDType::isLiteralType() const {
+bool AstNodeDType::isLiteralType() const VL_MT_SAFE {
     if (const auto* const dtypep = VN_CAST(skipRefp(), BasicDType)) {
         return dtypep->keyword().isLiteralType();
     } else if (const auto* const dtypep = VN_CAST(skipRefp(), UnpackArrayDType)) {
@@ -894,6 +908,29 @@ string AstScope::nameDotless() const {
     return out;
 }
 
+AstVarScope* AstScope::createTemp(const string& name, unsigned width) {
+    FileLine* const flp = fileline();
+    AstVar* const varp
+        = new AstVar{flp, VVarType::MODULETEMP, name, VFlagBitPacked{}, static_cast(width)};
+    modp()->addStmtsp(varp);
+    AstVarScope* const vscp = new AstVarScope{flp, this, varp};
+    addVarsp(vscp);
+    return vscp;
+}
+
+AstVarScope* AstScope::createTemp(const string& name, AstNodeDType* dtypep) {
+    FileLine* const flp = fileline();
+    AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
+    modp()->addStmtsp(varp);
+    AstVarScope* const vscp = new AstVarScope{flp, this, varp};
+    addVarsp(vscp);
+    return vscp;
+}
+
+AstVarScope* AstScope::createTempLike(const string& name, AstVarScope* vscp) {
+    return createTemp(name, vscp->dtypep());
+}
+
 string AstScopeName::scopePrettyNameFormatter(AstText* scopeTextp) const {
     string out;
     for (AstText* textp = scopeTextp; textp; textp = VN_AS(textp->nextp(), Text)) {
@@ -926,10 +963,10 @@ bool AstSenTree::hasClocked() const {
     }
     return false;
 }
-bool AstSenTree::hasSettle() const {
+bool AstSenTree::hasStatic() const {
     UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
     for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
-        if (senp->isSettle()) return true;
+        if (senp->isStatic()) return true;
     }
     return false;
 }
@@ -940,6 +977,13 @@ bool AstSenTree::hasInitial() const {
     }
     return false;
 }
+bool AstSenTree::hasFinal() const {
+    UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
+    for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
+        if (senp->isFinal()) return true;
+    }
+    return false;
+}
 bool AstSenTree::hasCombo() const {
     UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
     for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
@@ -947,6 +991,13 @@ bool AstSenTree::hasCombo() const {
     }
     return false;
 }
+bool AstSenTree::hasHybrid() const {
+    UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
+    for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
+        if (senp->isHybrid()) return true;
+    }
+    return false;
+}
 
 AstTypeTable::AstTypeTable(FileLine* fl)
     : ASTGEN_SUPER_TypeTable(fl) {
@@ -1297,7 +1348,10 @@ void AstNode::dump(std::ostream& str) const {
     }
 }
 
-void AstNodeProcedure::dump(std::ostream& str) const { this->AstNode::dump(str); }
+void AstNodeProcedure::dump(std::ostream& str) const {
+    this->AstNode::dump(str);
+    if (isSuspendable()) str << " [SUSP]";
+}
 
 void AstAlways::dump(std::ostream& str) const {
     this->AstNodeProcedure::dump(str);
@@ -1667,12 +1721,25 @@ void AstTimeImport::dump(std::ostream& str) const {
 void AstTypedef::dump(std::ostream& str) const {
     this->AstNode::dump(str);
     if (attrPublic()) str << " [PUBLIC]";
+    if (subDTypep()) {
+        str << " -> ";
+        subDTypep()->dump(str);
+    }
 }
 void AstNodeRange::dump(std::ostream& str) const { this->AstNode::dump(str); }
 void AstRange::dump(std::ostream& str) const {
     this->AstNodeRange::dump(str);
     if (littleEndian()) str << " [LITTLE]";
 }
+void AstParamTypeDType::dump(std::ostream& str) const {
+    this->AstNodeDType::dump(str);
+    if (subDTypep()) {
+        str << " -> ";
+        subDTypep()->dump(str);
+    } else {
+        str << " -> UNLINKED";
+    }
+}
 void AstRefDType::dump(std::ostream& str) const {
     this->AstNodeDType::dump(str);
     if (typedefp() || subDTypep()) {
@@ -1785,6 +1852,7 @@ const char* AstNetlist::broken() const {
     BROKEN_RTN(m_evalp && !m_evalp->brokeExists());
     BROKEN_RTN(m_dpiExportTriggerp && !m_dpiExportTriggerp->brokeExists());
     BROKEN_RTN(m_topScopep && !m_topScopep->brokeExists());
+    BROKEN_RTN(m_delaySchedulerp && !m_delaySchedulerp->brokeExists());
     return nullptr;
 }
 AstPackage* AstNetlist::dollarUnitPkgAddp() {
@@ -1940,7 +2008,6 @@ void AstVoidDType::dumpSmall(std::ostream& str) const {
 }
 void AstVarScope::dump(std::ostream& str) const {
     this->AstNode::dump(str);
-    if (isCircular()) str << " [CIRC]";
     if (isTrace()) str << " [T]";
     if (scopep()) str << " [scopep=" << reinterpret_cast(scopep()) << "]";
     if (varp()) {
@@ -1980,6 +2047,14 @@ void AstVarRef::dump(std::ostream& str) const {
 bool AstVarRef::same(const AstNode* samep) const {
     return same(static_cast(samep));
 }
+int AstVarRef::instrCount() const {
+    // Account for the target of hard-coded method calls as just an address computation
+    if (const AstCMethodHard* const callp = VN_CAST(backp(), CMethodHard)) {
+        if (callp->fromp() == this) return 1;
+    }
+    // Otherwise as a load/store
+    return widthInstrs() * (access().isReadOrRW() ? INSTR_COUNT_LD : 1);
+}
 void AstVar::dump(std::ostream& str) const {
     this->AstNode::dump(str);
     if (isSc()) str << " [SC]";
@@ -1992,7 +2067,6 @@ void AstVar::dump(std::ostream& str) const {
     if (isSigPublic()) str << " [P]";
     if (isLatched()) str << " [LATCHED]";
     if (isUsedLoopIdx()) str << " [LOOP]";
-    if (attrClockEn()) str << " [aCLKEN]";
     if (attrIsolateAssign()) str << " [aISO]";
     if (attrFileDescr()) str << " [aFD]";
     if (isFuncReturn()) {
@@ -2044,7 +2118,8 @@ void AstClassOrPackageRef::dump(std::ostream& str) const {
 }
 AstNodeModule* AstClassOrPackageRef::classOrPackagep() const {
     AstNode* foundp = m_classOrPackageNodep;
-    while (auto* const anodep = VN_CAST(foundp, Typedef)) foundp = anodep->subDTypep();
+    if (auto* const anodep = VN_CAST(foundp, Typedef)) foundp = anodep->subDTypep();
+    if (auto* const anodep = VN_CAST(foundp, NodeDType)) foundp = anodep->skipRefp();
     if (auto* const anodep = VN_CAST(foundp, ClassRefDType)) foundp = anodep->classp();
     return VN_CAST(foundp, NodeModule);
 }
@@ -2067,10 +2142,7 @@ const char* AstActive::broken() const {
     return nullptr;
 }
 void AstActive::cloneRelink() {
-    if (m_sensesp->clonep()) {
-        m_sensesp = m_sensesp->clonep();
-        UASSERT(m_sensesp, "Bad clone cross link: " << this);
-    }
+    if (m_sensesp->clonep()) m_sensesp = m_sensesp->clonep();
 }
 void AstNodeFTaskRef::dump(std::ostream& str) const {
     this->AstNodeStmt::dump(str);
@@ -2174,6 +2246,26 @@ void AstCFunc::dump(std::ostream& str) const {
     if (isConstructor()) str << " [CTOR]";
     if (isDestructor()) str << " [DTOR]";
     if (isVirtual()) str << " [VIRT]";
+    if (isCoroutine()) str << " [CORO]";
+}
+void AstCAwait::dump(std::ostream& str) const {
+    this->AstNodeStmt::dump(str);
+    if (sensesp()) {
+        str << " => ";
+        sensesp()->dump(str);
+    }
+}
+int AstCMethodHard::instrCount() const {
+    if (AstBasicDType* const basicp = fromp()->dtypep()->basicp()) {
+        // TODO: add a more structured description of library methods, rather than using string
+        //       matching. See #3715.
+        if (basicp->isTriggerVec() && m_name == "at") {
+            // This is an important special case for scheduling so we compute it precisely,
+            // it is simply a load.
+            return INSTR_COUNT_LD;
+        }
+    }
+    return AstNodeStmt::instrCount();
 }
 const char* AstCFunc::broken() const {
     BROKEN_RTN((m_scopep && !m_scopep->brokeExists()));
@@ -2187,3 +2279,22 @@ void AstCUse::dump(std::ostream& str) const {
     this->AstNode::dump(str);
     str << " [" << useType() << "]";
 }
+
+AstAlways* AstAssignW::convertToAlways() {
+    const bool hasTimingControl = isTimingControl();
+    AstNode* const lhs1p = lhsp()->unlinkFrBack();
+    AstNode* const rhs1p = rhsp()->unlinkFrBack();
+    AstNode* const controlp = timingControlp() ? timingControlp()->unlinkFrBack() : nullptr;
+    FileLine* const flp = fileline();
+    AstNode* bodysp = new AstAssign{flp, lhs1p, rhs1p, controlp};
+    if (hasTimingControl) {
+        // If there's a timing control, put the assignment in a fork..join_none. This process won't
+        // get marked as suspendable and thus will be scheduled normally
+        auto* forkp = new AstFork{flp, "", bodysp};
+        forkp->joinType(VJoinType::JOIN_NONE);
+        bodysp = forkp;
+    }
+    AstAlways* const newp = new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, bodysp};
+    replaceWith(newp);  // User expected to then deleteTree();
+    return newp;
+}
diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp
index 4cdb480e6..680f3b8b4 100644
--- a/src/V3Begin.cpp
+++ b/src/V3Begin.cpp
@@ -70,6 +70,7 @@ private:
     string m_namedScope;  // Name of begin blocks above us
     string m_unnamedScope;  // Name of begin blocks, including unnamed blocks
     int m_ifDepth = 0;  // Current if depth
+    bool m_underFork = false;  // True if the current statement is directly under a fork
 
     // METHODS
 
@@ -121,6 +122,12 @@ private:
     }
 
     // VISITORS
+    void visit(AstFork* nodep) override {
+        VL_RESTORER(m_underFork);
+        m_underFork = true;
+        dotNames(nodep, "__FORK__");
+        nodep->name("");
+    }
     void visit(AstNodeModule* nodep) override {
         VL_RESTORER(m_modp);
         {
@@ -170,11 +177,19 @@ private:
         VL_RESTORER(m_namedScope);
         VL_RESTORER(m_unnamedScope);
         {
-            dotNames(nodep, "__BEGIN__");
-
+            {
+                VL_RESTORER(m_underFork);
+                m_underFork = false;
+                dotNames(nodep, "__BEGIN__");
+            }
             UASSERT_OBJ(!nodep->genforp(), nodep, "GENFORs should have been expanded earlier");
 
             // Cleanup
+            if (m_underFork) {
+                // If we're under a fork, keep this begin to group its statements together
+                nodep->name("");
+                return;
+            }
             AstNode* addsp = nullptr;
             if (AstNode* const stmtsp = nodep->stmtsp()) {
                 stmtsp->unlinkFrBackWithNext();
@@ -248,6 +263,8 @@ private:
     }
     // VISITORS - LINT CHECK
     void visit(AstIf* nodep) override {  // not AstNodeIf; other types not covered
+        VL_RESTORER(m_underFork);
+        m_underFork = false;
         // Check IFDEPTH warning - could be in other transform files if desire
         VL_RESTORER(m_ifDepth);
         if (m_ifDepth == -1 || v3Global.opt.ifDepth() < 1) {  // Turned off
@@ -261,7 +278,11 @@ private:
         }
         iterateChildren(nodep);
     }
-    void visit(AstNode* nodep) override { iterateChildren(nodep); }
+    void visit(AstNode* nodep) override {
+        VL_RESTORER(m_underFork);
+        m_underFork = false;
+        iterateChildren(nodep);
+    }
 
 public:
     // CONSTRUCTORS
diff --git a/src/V3Broken.cpp b/src/V3Broken.cpp
index d039e593b..9b76b8d2b 100644
--- a/src/V3Broken.cpp
+++ b/src/V3Broken.cpp
@@ -51,7 +51,7 @@ static class BrokenCntGlobal {
     uint8_t m_count = MIN_VALUE;
 
 public:
-    uint8_t get() const {
+    uint8_t get() const VL_MT_SAFE {
         UASSERT(MIN_VALUE <= m_count && m_count <= MAX_VALUE, "Invalid generation number");
         return m_count;
     }
@@ -329,7 +329,7 @@ void V3Broken::brokenAll(AstNetlist* nodep) {
 
         // Mark every node in the tree
         const uint8_t brokenCntCurrent = s_brokenCntGlobal.get();
-        nodep->foreach([brokenCntCurrent](AstNode* nodep) {
+        nodep->foreach([brokenCntCurrent](AstNode* nodep) {
 #ifdef VL_LEAK_CHECKS
             UASSERT_OBJ(s_allocTable.isAllocated(nodep), nodep,
                         "AstNode is in tree, but not allocated");
diff --git a/src/V3CCtors.cpp b/src/V3CCtors.cpp
index c4af9097e..2dbc74121 100644
--- a/src/V3CCtors.cpp
+++ b/src/V3CCtors.cpp
@@ -188,7 +188,9 @@ void V3CCtors::cctorsAll() {
             for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) {
                 if (AstVar* const varp = VN_CAST(np, Var)) {
                     if (!varp->isIfaceParent() && !varp->isIfaceRef() && !varp->noReset()
-                        && !varp->isParam()) {
+                        && !varp->isParam()
+                        && !(varp->basicp()
+                             && (varp->basicp()->isEvent() || varp->basicp()->isTriggerVec()))) {
                         const auto vrefp = new AstVarRef{varp->fileline(), varp, VAccess::WRITE};
                         var_reset.add(new AstCReset{varp->fileline(), vrefp});
                     }
diff --git a/src/V3Cast.cpp b/src/V3Cast.cpp
index 0ab4f4202..8f7c303e3 100644
--- a/src/V3Cast.cpp
+++ b/src/V3Cast.cpp
@@ -159,8 +159,10 @@ private:
     void visit(AstVarRef* nodep) override {
         const AstNode* const backp = nodep->backp();
         if (nodep->access().isReadOnly() && !VN_IS(backp, CCast) && VN_IS(backp, NodeMath)
-            && !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor) && backp->width()
-            && castSize(nodep) != castSize(nodep->varp())) {
+            && !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor)
+            && (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec()
+                && !nodep->varp()->basicp()->isForkSync())
+            && backp->width() && castSize(nodep) != castSize(nodep->varp())) {
             // Cast vars to IData first, else below has upper bits wrongly set
             //  CData x=3; out = (QData)(x<<30);
             insertCast(nodep, castSize(nodep));
diff --git a/src/V3Cdc.cpp b/src/V3Cdc.cpp
index dc1e5a3e3..163a34002 100644
--- a/src/V3Cdc.cpp
+++ b/src/V3Cdc.cpp
@@ -644,8 +644,8 @@ private:
         UINFO(4, "  BLOCK  " << nodep << endl);
         AstNode::user2ClearTree();
         m_domainp = nodep->sensesp();
-        if (!m_domainp || m_domainp->hasCombo()
-            || m_domainp->hasClocked()) {  // IE not hasSettle/hasInitial
+        // Ignore static initializers, initial and final blocks
+        if (!m_domainp || m_domainp->hasCombo() || m_domainp->hasClocked()) {
             iterateNewStmt(nodep);
         }
         m_domainp = nullptr;
diff --git a/src/V3Changed.cpp b/src/V3Changed.cpp
deleted file mode 100644
index 41652250f..000000000
--- a/src/V3Changed.cpp
+++ /dev/null
@@ -1,258 +0,0 @@
-// -*- mode: C++; c-file-style: "cc-mode" -*-
-//*************************************************************************
-// DESCRIPTION: Verilator: Add temporaries, such as for changed nodes
-//
-// Code available from: https://verilator.org
-//
-//*************************************************************************
-//
-// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
-// can redistribute it and/or modify it under the terms of either the GNU
-// Lesser General Public License Version 3 or the Perl Artistic License
-// Version 2.0.
-// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
-//
-//*************************************************************************
-// V3Changed's Transformations:
-//
-// Each module:
-//      Each combo block
-//          For each variable that comes from combo block and is generated AFTER a usage
-//              Add __Vlast_{var} to local section, init to current value (just use array?)
-//              Change = if any var != last.
-//          If a signal is used as a clock in this module or any
-//          module *below*, and it isn't a input to this module,
-//          we need to indicate a new clock has been created.
-//
-//*************************************************************************
-
-#include "config_build.h"
-#include "verilatedos.h"
-
-#include "V3Changed.h"
-
-#include "V3Ast.h"
-#include "V3Global.h"
-
-#include 
-
-VL_DEFINE_DEBUG_FUNCTIONS;
-
-//######################################################################
-
-class ChangedState final {
-public:
-    // STATE
-    AstNodeModule* m_topModp = nullptr;  // Top module
-    AstScope* m_scopetopp = nullptr;  // Scope under TOPSCOPE
-    AstCFunc* m_chgFuncp = nullptr;  // Change function we're building
-    AstCFunc* m_tlChgFuncp = nullptr;  // Top level change function we're building
-    int m_numStmts = 0;  // Number of statements added to m_chgFuncp
-    int m_funcNum = 0;  // Number of change functions emitted
-    bool m_madeTopChg = false;
-
-    ChangedState() = default;
-    ~ChangedState() = default;
-
-    void maybeCreateChgFuncp() {
-        maybeCreateTopChg();
-        maybeCreateMidChg();
-    }
-    void maybeCreateTopChg() {
-        if (m_madeTopChg) return;
-        m_madeTopChg = true;
-        v3Global.rootp()->changeRequest(true);
-
-        // Create a wrapper change detection function that calls each change detection function
-        m_tlChgFuncp
-            = new AstCFunc{m_scopetopp->fileline(), "_change_request", m_scopetopp, "QData"};
-        m_tlChgFuncp->isStatic(false);
-        m_tlChgFuncp->isLoose(true);
-        m_tlChgFuncp->declPrivate(true);
-        m_scopetopp->addBlocksp(m_tlChgFuncp);
-        // Each change detection function needs at least one AstChangeDet
-        // to ensure that V3EmitC outputs the necessary code.
-        maybeCreateMidChg();
-        m_chgFuncp->addStmtsp(new AstChangeDet{m_scopetopp->fileline(), nullptr, nullptr});
-    }
-    void maybeCreateMidChg() {
-        // Don't create an extra function call if splitting is disabled
-        if (!v3Global.opt.outputSplitCFuncs()) {
-            m_chgFuncp = m_tlChgFuncp;
-            return;
-        }
-        if (!m_chgFuncp || v3Global.opt.outputSplitCFuncs() < m_numStmts) {
-            m_chgFuncp
-                = new AstCFunc{m_scopetopp->fileline(), "_change_request_" + cvtToStr(++m_funcNum),
-                               m_scopetopp, "QData"};
-            m_chgFuncp->isStatic(false);
-            m_chgFuncp->isLoose(true);
-            m_chgFuncp->declPrivate(true);
-            m_scopetopp->addBlocksp(m_chgFuncp);
-
-            // Add a top call to it
-            AstCCall* const callp = new AstCCall{m_scopetopp->fileline(), m_chgFuncp};
-
-            if (!m_tlChgFuncp->stmtsp()) {
-                m_tlChgFuncp->addStmtsp(new AstCReturn{m_scopetopp->fileline(), callp});
-            } else {
-                AstCReturn* const returnp = VN_AS(m_tlChgFuncp->stmtsp(), CReturn);
-                UASSERT_OBJ(returnp, m_scopetopp, "Lost CReturn in top change function");
-                // This is currently using AstLogOr which will shortcut the
-                // evaluation if any function returns true. This is likely what
-                // we want and is similar to the logic already in use inside
-                // V3EmitC, however, it also means that verbose logging may
-                // miss to print change detect variables.
-                AstNode* const newp = new AstCReturn{
-                    m_scopetopp->fileline(),
-                    new AstLogOr{m_scopetopp->fileline(), callp, returnp->lhsp()->unlinkFrBack()}};
-                returnp->replaceWith(newp);
-                VL_DO_DANGLING(returnp->deleteTree(), returnp);
-            }
-            m_numStmts = 0;
-        }
-    }
-};
-
-//######################################################################
-// Utility visitor to find elements to be compared
-
-class ChangedInsertVisitor final : public VNVisitor {
-private:
-    // STATE
-    ChangedState& m_state;  // Shared state across visitors
-    AstVarScope* const m_vscp;  // Original (non-change) variable we're change-detecting
-    AstVarScope* m_newvscp = nullptr;  // New (change detect) variable we're change-detecting
-    AstNode* m_varEqnp = nullptr;  // Original var's equation to get var value
-    AstNode* m_newLvEqnp = nullptr;  // New var's equation to read value
-    AstNode* m_newRvEqnp = nullptr;  // New var's equation to set value
-    uint32_t m_detects = 0;  // # detects created
-
-    // CONSTANTS
-    // How many indexes before error. Ok to increase this, but may result in much slower model
-    static constexpr uint32_t DETECTARRAY_MAX_INDEXES = 256;
-
-    void newChangeDet() {
-        if (++m_detects > DETECTARRAY_MAX_INDEXES) {
-            m_vscp->v3warn(E_DETECTARRAY,
-                           "Unsupported: Can't detect more than "
-                               << DETECTARRAY_MAX_INDEXES
-                               << " array indexes (probably with UNOPTFLAT warning suppressed): "
-                               << m_vscp->prettyName() << '\n'
-                               << m_vscp->warnMore()
-                               << "... Could recompile with DETECTARRAY_MAX_INDEXES increased");
-            return;
-        }
-        m_state.maybeCreateChgFuncp();
-
-        AstChangeDet* const changep = new AstChangeDet{
-            m_vscp->fileline(), m_varEqnp->cloneTree(true), m_newRvEqnp->cloneTree(true)};
-        m_state.m_chgFuncp->addStmtsp(changep);
-        AstAssign* const initp = new AstAssign{m_vscp->fileline(), m_newLvEqnp->cloneTree(true),
-                                               m_varEqnp->cloneTree(true)};
-        m_state.m_chgFuncp->addFinalsp(initp);
-
-        // Later code will expand words which adds to GCC compile time,
-        // so add penalty based on word width also
-        m_state.m_numStmts += initp->nodeCount() + m_varEqnp->widthWords();
-    }
-
-    void visit(AstBasicDType*) override {  //
-        newChangeDet();
-    }
-    void visit(AstPackArrayDType*) override {  //
-        newChangeDet();
-    }
-    void visit(AstUnpackArrayDType* nodep) override {
-        for (int index = 0; index < nodep->elementsConst(); ++index) {
-            VL_RESTORER(m_varEqnp);
-            VL_RESTORER(m_newLvEqnp);
-            VL_RESTORER(m_newRvEqnp);
-            {
-                m_varEqnp = new AstArraySel{nodep->fileline(), m_varEqnp->cloneTree(true), index};
-                m_newLvEqnp
-                    = new AstArraySel{nodep->fileline(), m_newLvEqnp->cloneTree(true), index};
-                m_newRvEqnp
-                    = new AstArraySel{nodep->fileline(), m_newRvEqnp->cloneTree(true), index};
-
-                iterate(nodep->subDTypep()->skipRefp());
-
-                m_varEqnp->deleteTree();
-                m_newLvEqnp->deleteTree();
-                m_newRvEqnp->deleteTree();
-            }
-        }
-    }
-    void visit(AstNodeUOrStructDType* nodep) override {
-        if (nodep->packedUnsup()) {
-            newChangeDet();
-        } else {
-            if (debug()) nodep->dumpTree(cout, "-DETECTARRAY-class-");
-            m_vscp->v3warn(E_DETECTARRAY, "Unsupported: Can't detect changes on complex variable"
-                                          " (probably with UNOPTFLAT warning suppressed): "
-                                              << m_vscp->varp()->prettyNameQ());
-        }
-    }
-    void visit(AstNode* nodep) override {
-        iterateChildren(nodep);
-        if (debug()) nodep->dumpTree(cout, "-DETECTARRAY-general-");
-        m_vscp->v3warn(E_DETECTARRAY, "Unsupported: Can't detect changes on complex variable"
-                                      " (probably with UNOPTFLAT warning suppressed): "
-                                          << m_vscp->varp()->prettyNameQ());
-    }
-
-public:
-    // CONSTRUCTORS
-    ChangedInsertVisitor(AstVarScope* vscp, ChangedState& state)
-        : m_state{state}
-        , m_vscp{vscp} {
-        // DPI export trigger should never need change detect. See similar assertions in V3Order
-        // (OrderVisitor::nodeMarkCircular), and V3GenClk (GenClkRenameVisitor::genInpClk).
-        UASSERT_OBJ(vscp != v3Global.rootp()->dpiExportTriggerp(), vscp,
-                    "DPI export trigger should not need change detect");
-        {
-            AstVar* const varp = m_vscp->varp();
-            const string newvarname{"__Vchglast__" + m_vscp->scopep()->nameDotless() + "__"
-                                    + varp->shortName()};
-            // Create:  VARREF(_last)
-            //          ASSIGN(VARREF(_last), VARREF(var))
-            //          ...
-            //          CHANGEDET(VARREF(_last), VARREF(var))
-            AstVar* const newvarp
-                = new AstVar{varp->fileline(), VVarType::MODULETEMP, newvarname, varp};
-            m_state.m_topModp->addStmtsp(newvarp);
-            m_newvscp = new AstVarScope{m_vscp->fileline(), m_state.m_scopetopp, newvarp};
-            m_state.m_scopetopp->addVarsp(m_newvscp);
-
-            m_varEqnp = new AstVarRef{m_vscp->fileline(), m_vscp, VAccess::READ};
-            m_newLvEqnp = new AstVarRef{m_vscp->fileline(), m_newvscp, VAccess::WRITE};
-            m_newRvEqnp = new AstVarRef{m_vscp->fileline(), m_newvscp, VAccess::READ};
-        }
-        iterate(vscp->dtypep()->skipRefp());
-        m_varEqnp->deleteTree();
-        m_newLvEqnp->deleteTree();
-        m_newRvEqnp->deleteTree();
-    }
-    ~ChangedInsertVisitor() override = default;
-    VL_UNCOPYABLE(ChangedInsertVisitor);
-};
-
-//######################################################################
-// Changed class functions
-
-void V3Changed::changedAll(AstNetlist* nodep) {
-    UINFO(2, __FUNCTION__ << ": " << endl);
-
-    ChangedState state;
-    state.m_scopetopp = nodep->topScopep()->scopep();
-    state.m_topModp = nodep->topModulep();
-    nodep->foreach([&state](AstVarScope* vscp) {
-        if (vscp->isCircular()) {
-            vscp->v3warn(IMPERFECTSCH,
-                         "Imperfect scheduling of variable: " << vscp->prettyNameQ());
-            ChangedInsertVisitor{vscp, state};
-        }
-    });
-
-    V3Global::dumpCheckGlobalTree("changed", 0, dumpTree() >= 3);
-}
diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp
index af7f6dd79..31661a277 100644
--- a/src/V3Clock.cpp
+++ b/src/V3Clock.cpp
@@ -34,6 +34,7 @@
 
 #include "V3Ast.h"
 #include "V3Global.h"
+#include "V3Sched.h"
 
 #include 
 
@@ -72,118 +73,43 @@ public:
 
 class ClockVisitor final : public VNVisitor {
 private:
-    // NODE STATE
-    // Cleared each Module:
-    //  AstVarScope::user1p()   -> AstVarScope*.  Temporary signal that was created.
-    const VNUser1InUse m_inuser1;
-
     // STATE
-    AstNodeModule* m_modp = nullptr;  // Current module
-    const AstTopScope* m_topScopep = nullptr;  // Current top scope
+    AstCFunc* m_evalp = nullptr;  // The '_eval' function
     AstScope* m_scopep = nullptr;  // Current scope
-    AstCFunc* m_evalFuncp = nullptr;  // Top eval function we are creating
-    AstCFunc* m_initFuncp = nullptr;  // Top initial function we are creating
-    AstCFunc* m_finalFuncp = nullptr;  // Top final function we are creating
-    AstCFunc* m_settleFuncp = nullptr;  // Top settlement function we are creating
     AstSenTree* m_lastSenp = nullptr;  // Last sensitivity match, so we can detect duplicates.
     AstIf* m_lastIfp = nullptr;  // Last sensitivity if active to add more under
-    AstMTaskBody* m_mtaskBodyp = nullptr;  // Current mtask body
+    bool m_inSampled = false;  // True inside a sampled expression
 
     // METHODS
 
-    AstVarScope* getCreateLastClk(AstVarScope* vscp) {
-        if (vscp->user1p()) return static_cast(vscp->user1p());
-        const AstVar* const varp = vscp->varp();
-        if (!varp->width1()) {
-            varp->v3warn(E_UNSUPPORTED, "Unsupported: Clock edge on non-single bit signal: "
-                                            << varp->prettyNameQ());
-        }
-        const string newvarname
-            = (string("__Vclklast__") + vscp->scopep()->nameDotless() + "__" + varp->name());
-        AstVar* const newvarp = new AstVar(vscp->fileline(), VVarType::MODULETEMP, newvarname,
-                                           VFlagLogicPacked{}, 1);
-        newvarp->noReset(true);  // Reset by below assign
-        m_modp->addStmtsp(newvarp);
-        AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopep, newvarp);
-        vscp->user1p(newvscp);
-        m_scopep->addVarsp(newvscp);
-        // Add init
-        AstNode* fromp = new AstVarRef(newvarp->fileline(), vscp, VAccess::READ);
-        if (v3Global.opt.xInitialEdge()) fromp = new AstNot(fromp->fileline(), fromp);
-        AstNode* const newinitp = new AstAssign(
-            vscp->fileline(), new AstVarRef(newvarp->fileline(), newvscp, VAccess::WRITE), fromp);
-        addToInitial(newinitp);
-        // At bottom, assign them
-        AstAssign* const finalp = new AstAssign(
-            vscp->fileline(), new AstVarRef(vscp->fileline(), newvscp, VAccess::WRITE),
-            new AstVarRef(vscp->fileline(), vscp, VAccess::READ));
-        m_evalFuncp->addFinalsp(finalp);
-        //
-        UINFO(4, "New Last: " << newvscp << endl);
-        return newvscp;
-    }
-    AstNode* createSenItemEquation(AstSenItem* nodep) {
-        // We know the var is clean, and one bit, so we use binary ops
-        // for speed instead of logical ops.
-        // POSEDGE:  var & ~var_last
-        // NEGEDGE:  ~var & var_last
-        // BOTHEDGE:  var ^ var_last
-        // HIGHEDGE:  var
-        // LOWEDGE:  ~var
-        // ANYEDGE:   var ^ var_last
-        AstNode* newp = nullptr;
-        if (nodep->edgeType() == VEdgeType::ET_ILLEGAL) {
-            nodep->v3warn(E_UNSUPPORTED,
-                          "Unsupported: Complicated event expression in sensitive activity list");
-            return nullptr;
-        }
-        UASSERT_OBJ(nodep->varrefp(), nodep, "No clock found on sense item");
-        AstVarScope* const clkvscp = nodep->varrefp()->varScopep();
-        if (nodep->edgeType() == VEdgeType::ET_POSEDGE) {
-            AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
-            newp = new AstAnd(
-                nodep->fileline(),
-                new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ),
-                new AstNot(nodep->fileline(),
-                           new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ)));
-        } else if (nodep->edgeType() == VEdgeType::ET_NEGEDGE) {
-            AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
-            newp = new AstAnd(
-                nodep->fileline(),
-                new AstNot(nodep->fileline(),
-                           new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(),
-                                         VAccess::READ)),
-                new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ));
-        } else if (nodep->edgeType() == VEdgeType::ET_BOTHEDGE) {
-            AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
-            newp = new AstXor(
-                nodep->fileline(),
-                new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ),
-                new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ));
-        } else if (nodep->edgeType() == VEdgeType::ET_HIGHEDGE) {
-            newp = new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ);
-        } else if (nodep->edgeType() == VEdgeType::ET_LOWEDGE) {
-            newp = new AstNot(nodep->fileline(),
-                              new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ));
-        } else {
-            nodep->v3fatalSrc("Bad edge type");
-        }
-        return newp;
-    }
     AstNode* createSenseEquation(AstSenItem* nodesp) {
-        // Nodep may be a list of elements; we need to walk it
         AstNode* senEqnp = nullptr;
         for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) {
-            AstNode* const senOnep = createSenItemEquation(senp);
-            if (senEqnp) {
-                // Add new OR to the sensitivity list equation
-                senEqnp = new AstOr(senp->fileline(), senEqnp, senOnep);
-            } else {
-                senEqnp = senOnep;
-            }
+            UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered");
+            AstNode* const senOnep = senp->sensp()->cloneTree(false);
+            senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
         }
         return senEqnp;
     }
+    AstVarScope* createSampledVar(AstVarScope* vscp) {
+        if (vscp->user1p()) return VN_AS(vscp->user1p(), VarScope);
+        const AstVar* const varp = vscp->varp();
+        const string newvarname
+            = string("__Vsampled__") + vscp->scopep()->nameDotless() + "__" + varp->name();
+        FileLine* const flp = vscp->fileline();
+        AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()};
+        newvarp->noReset(true);  // Reset by below assign
+        m_scopep->modp()->addStmtsp(newvarp);
+        AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp};
+        vscp->user1p(newvscp);
+        m_scopep->addVarsp(newvscp);
+        // At the top of _eval, assign them
+        AstAssign* const finalp = new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE},
+                                                new AstVarRef{flp, vscp, VAccess::READ}};
+        m_evalp->addInitsp(finalp);
+        UINFO(4, "New Sampled: " << newvscp << endl);
+        return newvscp;
+    }
     AstIf* makeActiveIf(AstSenTree* sensesp) {
         AstNode* const senEqnp = createSenseEquation(sensesp->sensesp());
         UASSERT_OBJ(senEqnp, sensesp, "No sense equation, shouldn't be in sequent activation.");
@@ -194,116 +120,7 @@ private:
         m_lastSenp = nullptr;
         m_lastIfp = nullptr;
     }
-    AstCFunc* makeTopFunction(const string& name, bool slow = false) {
-        AstCFunc* const funcp = new AstCFunc{m_topScopep->fileline(), name, m_topScopep->scopep()};
-        funcp->dontCombine(true);
-        funcp->isStatic(false);
-        funcp->isLoose(true);
-        funcp->entryPoint(true);
-        funcp->slow(slow);
-        funcp->isConst(false);
-        funcp->declPrivate(true);
-        m_topScopep->scopep()->addBlocksp(funcp);
-        return funcp;
-    }
-    void splitCheck(AstCFunc* ofuncp) {
-        if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
-        if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
-
-        int funcnum = 0;
-        int func_stmts = 0;
-        AstCFunc* funcp = nullptr;
-
-        // Unlink all statements, then add item by item to new sub-functions
-        AstBegin* const tempp = new AstBegin{ofuncp->fileline(), "[EditWrapper]",
-                                             ofuncp->stmtsp()->unlinkFrBackWithNext()};
-        if (ofuncp->finalsp()) tempp->addStmtsp(ofuncp->finalsp()->unlinkFrBackWithNext());
-        while (tempp->stmtsp()) {
-            AstNode* const itemp = tempp->stmtsp()->unlinkFrBack();
-            const int stmts = itemp->nodeCount();
-            if (!funcp || (func_stmts + stmts) > v3Global.opt.outputSplitCFuncs()) {
-                // Make a new function
-                funcp
-                    = new AstCFunc{ofuncp->fileline(), ofuncp->name() + "__" + cvtToStr(funcnum++),
-                                   m_topScopep->scopep()};
-                funcp->dontCombine(true);
-                funcp->isStatic(false);
-                funcp->isLoose(true);
-                funcp->slow(ofuncp->slow());
-                m_topScopep->scopep()->addBlocksp(funcp);
-                //
-                AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
-                ofuncp->addStmtsp(callp);
-                func_stmts = 0;
-            }
-            funcp->addStmtsp(itemp);
-            func_stmts += stmts;
-        }
-        VL_DO_DANGLING(tempp->deleteTree(), tempp);
-    }
-
     // VISITORS
-    void visit(AstTopScope* nodep) override {
-        UINFO(4, " TOPSCOPE   " << nodep << endl);
-        m_topScopep = nodep;
-        m_scopep = nodep->scopep();
-        UASSERT_OBJ(m_scopep, nodep,
-                    "No scope found on top level, perhaps you have no statements?");
-        // VV*****  We reset all user1p()
-        AstNode::user1ClearTree();
-        // Make top functions
-        m_evalFuncp = makeTopFunction("_eval");
-        m_initFuncp = makeTopFunction("_eval_initial", /* slow: */ true);
-        m_settleFuncp = makeTopFunction("_eval_settle", /* slow: */ true);
-        m_finalFuncp = makeTopFunction("_final", /* slow: */ true);
-        // Process the activates
-        iterateChildren(nodep);
-        UINFO(4, " TOPSCOPE iter done " << nodep << endl);
-        // Clear the DPI export trigger flag at the end of eval
-        if (AstVarScope* const dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp()) {
-            FileLine* const fl = dpiExportTriggerp->fileline();
-            AstAssign* const assignp
-                = new AstAssign{fl, new AstVarRef{fl, dpiExportTriggerp, VAccess::WRITE},
-                                new AstConst{fl, AstConst::BitFalse{}}};
-            m_evalFuncp->addFinalsp(assignp);
-        }
-        // Split large functions
-        splitCheck(m_evalFuncp);
-        splitCheck(m_initFuncp);
-        splitCheck(m_finalFuncp);
-        splitCheck(m_settleFuncp);
-        // Done, clear so we can detect errors
-        UINFO(4, " TOPSCOPEDONE " << nodep << endl);
-        clearLastSen();
-        m_topScopep = nullptr;
-        m_scopep = nullptr;
-    }
-    void visit(AstNodeModule* nodep) override {
-        // UINFO(4, " MOD   " << nodep << endl);
-        VL_RESTORER(m_modp);
-        {
-            m_modp = nodep;
-            iterateChildren(nodep);
-        }
-    }
-    void visit(AstScope* nodep) override {
-        // UINFO(4, " SCOPE   " << nodep << endl);
-        m_scopep = nodep;
-        iterateChildren(nodep);
-        if (AstNode* const movep = nodep->finalClksp()) {
-            UASSERT_OBJ(m_topScopep, nodep, "Final clocks under non-top scope");
-            movep->unlinkFrBackWithNext();
-            m_evalFuncp->addFinalsp(movep);
-        }
-        m_scopep = nullptr;
-    }
-    void visit(AstNodeProcedure* nodep) override {
-        if (AstNode* const stmtsp = nodep->stmtsp()) {
-            stmtsp->unlinkFrBackWithNext();
-            nodep->addNextHere(stmtsp);
-        }
-        VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
-    }
     void visit(AstCoverToggle* nodep) override {
         // nodep->dumpTree(cout, "ct:");
         // COVERTOGGLE(INC, ORIG, CHANGE) ->
@@ -321,110 +138,70 @@ private:
         nodep->replaceWith(newp);
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
-    void visit(AstCFunc* nodep) override {
-        iterateChildren(nodep);
-        // Link to global function
-        if (nodep->isFinal()) {
-            UINFO(4, "    isFinal " << nodep << endl);
-            AstCCall* const callp = new AstCCall(nodep->fileline(), nodep);
-            m_finalFuncp->addStmtsp(callp);
-        }
-    }
     void visit(AstSenTree* nodep) override {
-        // Delete it later; Actives still pointing to it
         nodep->unlinkFrBack();
-        pushDeletep(nodep);
-    }
-    void addToEvalLoop(AstNode* stmtsp) {
-        m_evalFuncp->addStmtsp(stmtsp);  // add to top level function
-    }
-    void addToSettleLoop(AstNode* stmtsp) {
-        m_settleFuncp->addStmtsp(stmtsp);  // add to top level function
-    }
-    void addToInitial(AstNode* stmtsp) {
-        m_initFuncp->addStmtsp(stmtsp);  // add to top level function
+        pushDeletep(nodep);  // Delete it later, AstActives still pointing to it
     }
     void visit(AstActive* nodep) override {
-        // Careful if adding variables here, ACTIVES can be under other ACTIVES
-        // Need to save and restore any member state in AstUntilStable block
-        if (!m_topScopep || !nodep->stmtsp()) {
-            // Not at the top or empty block...
-            // Only empty blocks should be leftover on the non-top.  Killem.
-            UASSERT_OBJ(!nodep->stmtsp(), nodep, "Non-empty lower active");
-            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
-        } else if (m_mtaskBodyp) {
-            UINFO(4, "  TR ACTIVE  " << nodep << endl);
-            AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
-            if (nodep->hasClocked()) {
-                UASSERT_OBJ(!nodep->hasInitial(), nodep,
-                            "Initial block should not have clock sensitivity");
-                if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) {
-                    UINFO(4, "    sameSenseTree\n");
-                } else {
-                    clearLastSen();
-                    m_lastSenp = nodep->sensesp();
-                    // Make a new if statement
-                    m_lastIfp = makeActiveIf(m_lastSenp);
-                    m_mtaskBodyp->addStmtsp(m_lastIfp);
-                }
-                // Move statements to if
-                m_lastIfp->addThensp(stmtsp);
-            } else if (nodep->hasInitial() || nodep->hasSettle()) {
-                nodep->v3fatalSrc("MTask should not include initial/settle logic.");
-            } else {
-                // Combo logic. Move statements to mtask func.
-                clearLastSen();
-                m_mtaskBodyp->addStmtsp(stmtsp);
-            }
-            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
-        } else {
-            UINFO(4, "  ACTIVE  " << nodep << endl);
-            AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
-            if (nodep->hasClocked()) {
-                // Remember the latest sensitivity so we can compare it next time
-                UASSERT_OBJ(!nodep->hasInitial(), nodep,
-                            "Initial block should not have clock sensitivity");
-                if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) {
-                    UINFO(4, "    sameSenseTree\n");
-                } else {
-                    clearLastSen();
-                    m_lastSenp = nodep->sensesp();
-                    // Make a new if statement
-                    m_lastIfp = makeActiveIf(m_lastSenp);
-                    addToEvalLoop(m_lastIfp);
-                }
-                // Move statements to if
-                m_lastIfp->addThensp(stmtsp);
-            } else if (nodep->hasInitial()) {
-                // Don't need to: clearLastSen();, as we're adding it to different cfunc
-                // Move statements to function
-                addToInitial(stmtsp);
-            } else if (nodep->hasSettle()) {
-                // Don't need to: clearLastSen();, as we're adding it to different cfunc
-                // Move statements to function
-                addToSettleLoop(stmtsp);
-            } else {
-                // Combo
-                clearLastSen();
-                // Move statements to function
-                addToEvalLoop(stmtsp);
-            }
-            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
+        UASSERT_OBJ(nodep->hasClocked(), nodep, "Should have been converted by V3Sched");
+        UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty");
+
+        VNRelinker relinker;
+        nodep->unlinkFrBack(&relinker);
+        AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
+
+        // Create 'if' statement, if needed
+        if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) {
+            clearLastSen();
+            m_lastSenp = nodep->sensesp();
+            // Make a new if statement
+            m_lastIfp = makeActiveIf(m_lastSenp);
+            relinker.relink(m_lastIfp);
         }
+
+        // Move statements to if
+        m_lastIfp->addThensp(stmtsp);
+
+        // Dispose of the AstActive
+        VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
     void visit(AstExecGraph* nodep) override {
-        VL_RESTORER(m_mtaskBodyp);
-        for (m_mtaskBodyp = nodep->mTaskBodiesp(); m_mtaskBodyp;
-             m_mtaskBodyp = VN_AS(m_mtaskBodyp->nextp(), MTaskBody)) {
+        for (AstMTaskBody* mtaskBodyp = nodep->mTaskBodiesp(); mtaskBodyp;
+             mtaskBodyp = VN_AS(mtaskBodyp->nextp(), MTaskBody)) {
             clearLastSen();
-            iterate(m_mtaskBodyp);
+            iterate(mtaskBodyp);
         }
         clearLastSen();
-        // Move the ExecGraph into _eval. Its location marks the
-        // spot where the graph will execute, relative to other
-        // (serial) logic in the cycle.
-        nodep->unlinkFrBack();
-        addToEvalLoop(nodep);
+    }
+
+    //========== Create sampled values
+    void visit(AstScope* nodep) override {
+        VL_RESTORER(m_scopep);
+        {
+            m_scopep = nodep;
+            iterateChildren(nodep);
+        }
+    }
+    void visit(AstSampled* nodep) override {
+        VL_RESTORER(m_inSampled);
+        {
+            m_inSampled = true;
+            iterateChildren(nodep);
+            nodep->replaceWith(nodep->exprp()->unlinkFrBack());
+            VL_DO_DANGLING(pushDeletep(nodep), nodep);
+        }
+    }
+    void visit(AstVarRef* nodep) override {
+        iterateChildren(nodep);
+        if (m_inSampled && !nodep->user1SetOnce()) {
+            UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "Should have failed in V3Access");
+            AstVarScope* const varscp = nodep->varScopep();
+            AstVarScope* const lastscp = createSampledVar(varscp);
+            AstNode* const newp = new AstVarRef{nodep->fileline(), lastscp, VAccess::READ};
+            newp->user1SetOnce();  // Don't sample this one
+            nodep->replaceWith(newp);
+            VL_DO_DANGLING(pushDeletep(nodep), nodep);
+        }
     }
 
     //--------------------
@@ -432,11 +209,9 @@ private:
 
 public:
     // CONSTRUCTORS
-    explicit ClockVisitor(AstNetlist* nodep) {
-        iterate(nodep);
-        // Allow downstream modules to find _eval()
-        // easily without iterating through the tree.
-        nodep->evalp(m_evalFuncp);
+    explicit ClockVisitor(AstNetlist* netlistp) {
+        m_evalp = netlistp->evalp();
+        iterate(netlistp);
     }
     ~ClockVisitor() override = default;
 };
diff --git a/src/V3Common.cpp b/src/V3Common.cpp
index 9961585b9..907c5a33d 100644
--- a/src/V3Common.cpp
+++ b/src/V3Common.cpp
@@ -47,6 +47,19 @@ static void makeVlToString(AstClass* nodep) {
     funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});
     nodep->addStmtsp(funcp);
 }
+static void makeVlToString(AstIface* nodep) {
+    AstCFunc* const funcp
+        = new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
+    funcp->argTypes("const " + EmitCBaseVisitor::prefixNameProtect(nodep) + "* obj");
+    funcp->isMethod(false);
+    funcp->isConst(false);
+    funcp->isStatic(false);
+    funcp->protect(false);
+    AstNode* const exprp = new AstCMath{nodep->fileline(), "obj ? obj->name() : \"null\"", 0};
+    exprp->dtypeSetString();
+    funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});
+    nodep->addStmtsp(funcp);
+}
 static void makeToString(AstClass* nodep) {
     AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string", nullptr, "std::string"};
     funcp->isConst(true);
@@ -117,6 +130,8 @@ void V3Common::commonAll() {
             makeVlToString(classp);
             makeToString(classp);
             makeToStringMiddle(classp);
+        } else if (AstIface* const ifacep = VN_CAST(nodep, Iface)) {
+            makeVlToString(ifacep);
         }
     }
     V3Global::dumpCheckGlobalTree("common", 0, dumpTree() >= 3);
diff --git a/src/V3Config.cpp b/src/V3Config.cpp
index abfd4f295..73d1ed934 100644
--- a/src/V3Config.cpp
+++ b/src/V3Config.cpp
@@ -332,7 +332,8 @@ public:
     }
     bool waive(V3ErrorCode code, const string& match) {
         for (const auto& itr : m_waivers) {
-            if (((itr.first == code) || (itr.first == V3ErrorCode::I_LINT))
+            if (((itr.first == code) || (itr.first == V3ErrorCode::I_LINT)
+                 || (code.unusedError() && itr.first == V3ErrorCode::I_UNUSED))
                 && VString::wildmatch(match, itr.second)) {
                 return true;
             }
@@ -563,7 +564,7 @@ void V3Config::addVarAttr(FileLine* fl, const string& module, const string& ftas
     } else {
         if (attr == VAttrType::VAR_FORCEABLE) {
             if (module.empty()) {
-                fl->v3error("missing -module");
+                fl->v3error("forceable missing -module");
             } else if (!ftask.empty()) {
                 fl->v3error("Signals inside functions/tasks cannot be marked forceable");
             } else {
diff --git a/src/V3Const.cpp b/src/V3Const.cpp
index f091e04bc..9d1b7f7f8 100644
--- a/src/V3Const.cpp
+++ b/src/V3Const.cpp
@@ -2038,10 +2038,10 @@ private:
                 // Note only do this (need user4) when m_warn, which is
                 // done as unique visitor
                 const VNUser4InUse m_inuser4;
-                nodep->lhsp()->foreach([](const AstVarRef* nodep) {
+                nodep->lhsp()->foreach([](const AstVarRef* nodep) {
                     if (nodep->varp()) nodep->varp()->user4(1);
                 });
-                nodep->rhsp()->foreach([&need_temp](const AstVarRef* nodep) {
+                nodep->rhsp()->foreach([&need_temp](const AstVarRef* nodep) {
                     if (nodep->varp() && nodep->varp()->user4()) need_temp = true;
                 });
             }
@@ -2411,12 +2411,21 @@ private:
             VL_DO_DANGLING(lsb1p->deleteTree(), lsb1p);
             VL_DO_DANGLING(lsb2p->deleteTree(), lsb2p);
         } else {
-            // Width is important, we need the width of the fromp's
-            // expression, not the potentially smaller lsb1p's width
-            newlsbp
-                = new AstAdd(lsb1p->fileline(), lsb2p, new AstExtend(lsb1p->fileline(), lsb1p));
-            newlsbp->dtypeFrom(lsb2p);  // Unsigned
-            VN_AS(newlsbp, Add)->rhsp()->dtypeFrom(lsb2p);
+            // Width is important, we need the width of the fromp's expression, not the
+            // potentially smaller lsb1p's width, but don't insert a redundant AstExtend.
+            // Note that due to some sloppiness in earlier passes, lsb1p might actually be wider,
+            // so extend to the wider type.
+            AstNode* const widep = lsb1p->width() > lsb2p->width() ? lsb1p : lsb2p;
+            AstNode* const lhsp = widep->width() > lsb2p->width()
+                                      ? new AstExtend{lsb2p->fileline(), lsb2p}
+                                      : lsb2p;
+            AstNode* const rhsp = widep->width() > lsb1p->width()
+                                      ? new AstExtend{lsb1p->fileline(), lsb1p}
+                                      : lsb1p;
+            lhsp->dtypeFrom(widep);
+            rhsp->dtypeFrom(widep);
+            newlsbp = new AstAdd{lsb1p->fileline(), lhsp, rhsp};
+            newlsbp->dtypeFrom(widep);
         }
         AstSel* const newp = new AstSel(nodep->fileline(), fromp, newlsbp, widthp);
         nodep->replaceWith(newp);
@@ -2606,7 +2615,7 @@ private:
                      && m_doNConst
                      && v3Global.opt.fConst()
                      // Default value, not a "known" constant for this usage
-                     && !nodep->varp()->isClassMember()
+                     && !nodep->varp()->isClassMember() && !nodep->varp()->isUsedVirtIface()
                      && !(nodep->varp()->isFuncLocal() && nodep->varp()->isNonOutput())
                      && !nodep->varp()->noSubst() && !nodep->varp()->isSigPublic())
                     || nodep->varp()->isParam())) {
@@ -2688,7 +2697,14 @@ private:
             // Constants in sensitivity lists may be removed (we'll simplify later)
             if (nodep->isClocked()) {  // A constant can never get a pos/negedge
                 if (onlySenItemInSenTree(nodep)) {
-                    nodep->replaceWith(new AstSenItem(nodep->fileline(), AstSenItem::Never()));
+                    if (nodep->edgeType() == VEdgeType::ET_CHANGED) {
+                        // TODO: This really is dodgy, as strictgly compliant simulators will not
+                        //       execute this block, but but t_func_check relies on it
+                        nodep->replaceWith(
+                            new AstSenItem{nodep->fileline(), AstSenItem::Initial{}});
+                    } else {
+                        nodep->replaceWith(new AstSenItem{nodep->fileline(), AstSenItem::Never{}});
+                    }
                     VL_DO_DANGLING(nodep->deleteTree(), nodep);
                 } else {
                     VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
@@ -2708,16 +2724,8 @@ private:
             }
             UINFO(8, "senItem(NOT...) " << nodep << " " << invert << endl);
             if (invert) nodep->edgeType(nodep->edgeType().invert());
-            AstNodeVarRef* const senvarp = VN_AS(lastSensp->unlinkFrBack(), NodeVarRef);
-            UASSERT_OBJ(senvarp, sensp, "Non-varref sensitivity variable");
-            sensp->replaceWith(senvarp);
+            sensp->replaceWith(lastSensp->unlinkFrBack());
             VL_DO_DANGLING(sensp->deleteTree(), sensp);
-        } else if (!m_doNConst  // Deal with later when doNConst missing
-                   && (VN_IS(nodep->sensp(), EnumItemRef) || VN_IS(nodep->sensp(), Const))) {
-        } else if (nodep->isIllegal()) {  // Deal with later
-        } else {
-            UASSERT_OBJ(!(nodep->hasVar() && !nodep->varrefp()), nodep,
-                        "Null sensitivity variable");
         }
     }
 
@@ -2726,8 +2734,10 @@ private:
             if (lhsp->type() < rhsp->type()) return true;
             if (lhsp->type() > rhsp->type()) return false;
             // Looks visually better if we keep sorted by name
-            if (!lhsp->varrefp() && rhsp->varrefp()) return true;
-            if (lhsp->varrefp() && !rhsp->varrefp()) return false;
+            if (!lhsp->sensp() && rhsp->sensp()) return true;
+            if (lhsp->sensp() && !rhsp->sensp()) return false;
+            if (lhsp->varrefp() && !rhsp->varrefp()) return true;
+            if (!lhsp->varrefp() && rhsp->varrefp()) return false;
             if (lhsp->varrefp() && rhsp->varrefp()) {
                 if (lhsp->varrefp()->name() < rhsp->varrefp()->name()) return true;
                 if (lhsp->varrefp()->name() > rhsp->varrefp()->name()) return false;
@@ -2737,6 +2747,27 @@ private:
                 // Or rarely, different data types
                 if (lhsp->varrefp()->dtypep() < rhsp->varrefp()->dtypep()) return true;
                 if (lhsp->varrefp()->dtypep() > rhsp->varrefp()->dtypep()) return false;
+            } else if (AstCMethodHard* const lp = VN_CAST(lhsp->sensp(), CMethodHard)) {
+                if (AstCMethodHard* const rp = VN_CAST(rhsp->sensp(), CMethodHard)) {
+                    if (AstVarRef* const lRefp = VN_CAST(lp->fromp(), VarRef)) {
+                        if (AstVarRef* const rRefp = VN_CAST(rp->fromp(), VarRef)) {
+                            if (lRefp->name() < rRefp->name()) return true;
+                            if (lRefp->name() > rRefp->name()) return false;
+                            // But might be same name with different scopes
+                            if (lRefp->varScopep() < rRefp->varScopep()) return true;
+                            if (lRefp->varScopep() > rRefp->varScopep()) return false;
+                            // Or rarely, different data types
+                            if (lRefp->dtypep() < rRefp->dtypep()) return true;
+                            if (lRefp->dtypep() > rRefp->dtypep()) return false;
+                        }
+                    }
+                    if (AstConst* lConstp = VN_CAST(lp->pinsp(), Const)) {
+                        if (AstConst* rConstp = VN_CAST(rp->pinsp(), Const)) {
+                            if (lConstp->toUInt() < rConstp->toUInt()) return true;
+                            if (lConstp->toUInt() > rConstp->toUInt()) return false;
+                        }
+                    }
+                }
             }
             // Sort by edge, AFTER variable, as we want multiple edges for same var adjacent.
             // note the SenTree optimizer requires this order (more
@@ -2799,17 +2830,13 @@ private:
                 AstSenItem* const litemp = senp;
                 AstSenItem* const ritemp = nextp;
                 if (ritemp) {
-                    if ((litemp->varrefp() && ritemp->varrefp()
-                         && litemp->varrefp()->sameGateTree(ritemp->varrefp()))
-                        || (!litemp->varrefp() && !ritemp->varrefp())) {
+                    if ((litemp->sensp() && ritemp->sensp()
+                         && litemp->sensp()->sameGateTree(ritemp->sensp()))
+                        || (!litemp->sensp() && !ritemp->sensp())) {
                         // We've sorted in the order ANY, BOTH, POS, NEG,
                         // so we don't need to try opposite orders
-                        if ((litemp->edgeType()
-                             == VEdgeType::ET_ANYEDGE)  // ANY  or {BOTH|POS|NEG} -> ANY
-                            || (litemp->edgeType()
-                                == VEdgeType::ET_BOTHEDGE)  // BOTH or {POS|NEG} -> BOTH
-                            || (litemp->edgeType() == VEdgeType::ET_POSEDGE  // POS  or NEG -> BOTH
-                                && ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
+                        if ((litemp->edgeType() == VEdgeType::ET_POSEDGE  // POS or NEG -> BOTH
+                             && ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
                             || (litemp->edgeType() == ritemp->edgeType())  // Identical edges
                         ) {
                             // Fix edge of old node
@@ -2833,6 +2860,7 @@ private:
     // Zero elimination
     void visit(AstNodeAssign* nodep) override {
         iterateChildren(nodep);
+        if (nodep->timingControlp()) m_hasJumpDelay = true;
         if (m_doNConst && replaceNodeAssign(nodep)) return;
     }
     void visit(AstAssignAlias* nodep) override {
@@ -3147,10 +3175,6 @@ private:
     //-----
     // Jump elimination
 
-    void visit(AstDelay* nodep) override {
-        iterateChildren(nodep);
-        m_hasJumpDelay = true;
-    }
     void visit(AstJumpGo* nodep) override {
         iterateChildren(nodep);
         // Jump to label where label immediately follows label is not useful
@@ -3546,6 +3570,7 @@ private:
                                << nodep->prettyTypeName() << " to constant.");
             }
         } else {
+            if (nodep->isTimingControl()) m_hasJumpDelay = true;
             // Calculate the width of this operation
             if (m_params && !nodep->width()) nodep = V3Width::widthParamsEdit(nodep);
             iterateChildren(nodep);
diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp
index 324e6f7a4..8a5781ddc 100644
--- a/src/V3Coverage.cpp
+++ b/src/V3Coverage.cpp
@@ -128,7 +128,7 @@ private:
             AstVar* const varp = new AstVar(incp->fileline(), VVarType::MODULETEMP, trace_var_name,
                                             incp->findUInt32DType());
             varp->trace(true);
-            varp->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true);
+            varp->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true);
             m_modp->addStmtsp(varp);
             UINFO(5, "New coverage trace: " << varp << endl);
             AstAssign* const assp = new AstAssign(
@@ -283,7 +283,7 @@ private:
                 const string newvarname = std::string{"__Vtogcov__"} + nodep->shortName();
                 AstVar* const chgVarp
                     = new AstVar(nodep->fileline(), VVarType::MODULETEMP, newvarname, nodep);
-                chgVarp->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true);
+                chgVarp->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true);
                 m_modp->addStmtsp(chgVarp);
 
                 // Create bucket for each dimension * bit.
diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp
index 83ecf5ffa..32dedc071 100644
--- a/src/V3Dead.cpp
+++ b/src/V3Dead.cpp
@@ -133,7 +133,7 @@ private:
         // Class packages might have no children, but need to remain as
         // long as the class they refer to is needed
         if (VN_IS(m_modp, Class) || VN_IS(m_modp, ClassPackage)) nodep->user1Inc();
-        if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp() && !nodep->finalClksp()) {
+        if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp()) {
             m_scopesp.push_back(nodep);
         }
     }
@@ -198,6 +198,19 @@ private:
         }
         if (nodep->classp()) nodep->classp()->user1Inc();
     }
+    void visit(AstIfaceRefDType* nodep) override {
+        iterateChildren(nodep);
+        checkDType(nodep);
+        checkAll(nodep);
+        if (nodep->modportp()) {
+            if (m_elimCells) {
+                nodep->modportp(nullptr);
+            } else {
+                nodep->modportp()->user1Inc();
+            }
+        }
+        if (nodep->ifaceViaCellp()) nodep->ifaceViaCellp()->user1Inc();
+    }
     void visit(AstNodeDType* nodep) override {
         iterateChildren(nodep);
         checkDType(nodep);
@@ -281,6 +294,7 @@ private:
             } else {  // Track like any other statement
                 iterateAndNextNull(nodep->lhsp());
             }
+            iterateNull(nodep->timingControlp());
         }
     }
 
@@ -308,7 +322,7 @@ private:
                     // And its children may now be killable too; correct counts
                     // Recurse, as cells may not be directly under the module but in a generate
                     if (!modp->dead()) {  // If was dead didn't increment user1's
-                        modp->foreach([](const AstCell* cellp) {  //
+                        modp->foreach([](const AstCell* cellp) {  //
                             cellp->modp()->user1Inc(-1);
                         });
                     }
@@ -320,7 +334,7 @@ private:
     }
     bool mightElimVar(AstVar* nodep) const {
         if (nodep->isSigPublic()) return false;  // Can't elim publics!
-        if (nodep->isIO() || nodep->isClassMember()) return false;
+        if (nodep->isIO() || nodep->isClassMember() || nodep->isUsedVirtIface()) return false;
         if (nodep->isTemp() && !nodep->isTrace()) return true;
         return m_elimUserVars;  // Post-Trace can kill most anything
     }
@@ -423,10 +437,46 @@ private:
         }
     }
 
+    void preserveTopIfaces(AstNetlist* rootp) {
+        for (AstNodeModule* modp = rootp->modulesp(); modp && modp->level() <= 2;
+             modp = VN_AS(modp->nextp(), NodeModule)) {
+            for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) {
+                if (AstVar* const varp = VN_CAST(subnodep, Var)) {
+                    if (varp->isIfaceRef()) {
+                        const AstNodeDType* const subtypep = varp->subDTypep();
+                        const AstIfaceRefDType* ifacerefp = nullptr;
+                        if (VN_IS(subtypep, IfaceRefDType)) {
+                            ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType);
+                        } else if (VN_IS(subtypep, BracketArrayDType)) {
+                            const AstBracketArrayDType* const arrp
+                                = VN_AS(subtypep, BracketArrayDType);
+                            const AstNodeDType* const arrsubtypep = arrp->subDTypep();
+                            if (VN_IS(arrsubtypep, IfaceRefDType)) {
+                                ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
+                            }
+                        } else if (VN_IS(subtypep, UnpackArrayDType)) {
+                            const AstUnpackArrayDType* const arrp
+                                = VN_AS(subtypep, UnpackArrayDType);
+                            const AstNodeDType* const arrsubtypep = arrp->subDTypep();
+                            if (VN_IS(arrsubtypep, IfaceRefDType)) {
+                                ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
+                            }
+                        }
+
+                        if (ifacerefp && !ifacerefp->cellp()
+                            && (ifacerefp->ifacep()->user1() == 0)) {
+                            ifacerefp->ifacep()->user1(1);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
 public:
     // CONSTRUCTORS
     DeadVisitor(AstNetlist* nodep, bool elimUserVars, bool elimDTypes, bool elimScopes,
-                bool elimCells)
+                bool elimCells, bool elimTopIfaces)
         : m_elimUserVars{elimUserVars}
         , m_elimDTypes{elimDTypes}
         , m_elimCells{elimCells} {
@@ -435,6 +485,11 @@ public:
         // Operate on whole netlist
         iterate(nodep);
 
+        if (AstVarScope* const vscp = nodep->dpiExportTriggerp()) {
+            vscp->user1Inc();
+            vscp->varp()->user1Inc();
+        }
+
         deadCheckVar();
         // We only eliminate scopes when in a flattened structure
         // Otherwise we have no easy way to know if a scope is used
@@ -442,6 +497,7 @@ public:
         if (elimCells) deadCheckCells();
         deadCheckClasses();
         // Modules after vars, because might be vars we delete inside a mod we delete
+        if (!elimTopIfaces) preserveTopIfaces(nodep);
         deadCheckMod();
 
         // We may have removed some datatypes, cleanup
@@ -455,30 +511,32 @@ public:
 
 void V3Dead::deadifyModules(AstNetlist* nodep) {
     UINFO(2, __FUNCTION__ << ": " << endl);
-    { DeadVisitor{nodep, false, false, false, false}; }  // Destruct before checking
+    {
+        DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported()};
+    }  // Destruct before checking
     V3Global::dumpCheckGlobalTree("deadModules", 0, dumpTree() >= 6);
 }
 
 void V3Dead::deadifyDTypes(AstNetlist* nodep) {
     UINFO(2, __FUNCTION__ << ": " << endl);
-    { DeadVisitor{nodep, false, true, false, false}; }  // Destruct before checking
+    { DeadVisitor{nodep, false, true, false, false, false}; }  // Destruct before checking
     V3Global::dumpCheckGlobalTree("deadDtypes", 0, dumpTree() >= 3);
 }
 
 void V3Dead::deadifyDTypesScoped(AstNetlist* nodep) {
     UINFO(2, __FUNCTION__ << ": " << endl);
-    { DeadVisitor{nodep, false, true, true, false}; }  // Destruct before checking
+    { DeadVisitor{nodep, false, true, true, false, false}; }  // Destruct before checking
     V3Global::dumpCheckGlobalTree("deadDtypesScoped", 0, dumpTree() >= 3);
 }
 
 void V3Dead::deadifyAll(AstNetlist* nodep) {
     UINFO(2, __FUNCTION__ << ": " << endl);
-    { DeadVisitor{nodep, true, true, false, true}; }  // Destruct before checking
+    { DeadVisitor{nodep, true, true, false, true, false}; }  // Destruct before checking
     V3Global::dumpCheckGlobalTree("deadAll", 0, dumpTree() >= 3);
 }
 
 void V3Dead::deadifyAllScoped(AstNetlist* nodep) {
     UINFO(2, __FUNCTION__ << ": " << endl);
-    { DeadVisitor{nodep, true, true, true, true}; }  // Destruct before checking
+    { DeadVisitor{nodep, true, true, true, true, false}; }  // Destruct before checking
     V3Global::dumpCheckGlobalTree("deadAllScoped", 0, dumpTree() >= 3);
 }
diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp
index 6a4e89ed8..c7012c72b 100644
--- a/src/V3Delayed.cpp
+++ b/src/V3Delayed.cpp
@@ -73,6 +73,8 @@ private:
     //  AstVarScope::user1p()   -> AstVarScope*.  Points to temp var created.
     //  AstVarScope::user2p()   -> AstActive*.  Points to activity block of signal
     //                                       (valid when AstVarScope::user1p is valid)
+    //  AstVarScope::user3p()   -> AstAlwaysPost*.  Post block for this variable used for
+    //                                              AssignDlys in suspendable processes
     //  AstVarScope::user4p()   -> AstAlwaysPost*.  Post block for this variable
     //  AstVarScope::user5p()   -> AstVarRef*. Last blocking or non-blocking reference
     //  AstVar::user2()         -> bool.  Set true if already made warning
@@ -94,9 +96,11 @@ private:
     AstActive* m_activep = nullptr;  // Current activate
     const AstCFunc* m_cfuncp = nullptr;  // Current public C Function
     AstAssignDly* m_nextDlyp = nullptr;  // Next delayed assignment in a list of assignments
+    AstNodeProcedure* m_procp = nullptr;  // Current process
+    std::set m_timingDomains;  // Timing resume domains
     bool m_inDly = false;  // True in delayed assignments
     bool m_inLoop = false;  // True in for loops
-    bool m_inInitial = false;  // True in initial blocks
+    bool m_inInitial = false;  // True in static initializers and initial blocks
     bool m_ignoreBlkAndNBlk = false;  // Suppress delayed assignment BLKANDNBLK
     using VarMap = std::map, AstVar*>;
     VarMap m_modVarMap;  // Table of new var names created under module
@@ -105,6 +109,11 @@ private:
 
     // METHODS
 
+    const AstNode* containingAssignment(const AstNode* nodep) {
+        while (nodep && !VN_IS(nodep, NodeAssign)) nodep = nodep->backp();
+        return nodep;
+    }
+
     void markVarUsage(AstNodeVarRef* nodep, bool blocking) {
         // Ignore if warning is disabled on this reference (used by V3Force).
         if (nodep->fileline()->warnIsOff(V3ErrorCode::BLKANDNBLK)) return;
@@ -118,8 +127,10 @@ private:
         } else {
             const bool last_was_blocking = lastrefp->user5();
             if (last_was_blocking != blocking) {
-                const AstNode* const nonblockingp = blocking ? nodep : lastrefp;
-                const AstNode* const blockingp = blocking ? lastrefp : nodep;
+                const AstNode* nonblockingp = blocking ? nodep : lastrefp;
+                if (const AstNode* np = containingAssignment(nonblockingp)) nonblockingp = np;
+                const AstNode* blockingp = blocking ? lastrefp : nodep;
+                if (const AstNode* np = containingAssignment(blockingp)) blockingp = np;
                 vscp->v3warn(
                     BLKANDNBLK,
                     "Unsupported: Blocked and non-blocking assignments to same variable: "
@@ -164,7 +175,7 @@ private:
         return varscp;
     }
 
-    AstActive* createActivePost(AstVarRef* varrefp) {
+    AstActive* createActive(AstNode* varrefp) {
         AstActive* const newactp
             = new AstActive(varrefp->fileline(), "sequentdly", m_activep->sensesp());
         // Was addNext(), but addNextHere() avoids a linear search.
@@ -204,7 +215,7 @@ private:
         }
     }
 
-    AstNode* createDlyArray(AstAssignDly* nodep, AstNode* lhsp) {
+    AstNode* createDlyOnSet(AstAssignDly* nodep, AstNode* lhsp) {
         // Create delayed assignment
         // See top of this file for transformation
         // Return the new LHS for the assignment, Null = unlink
@@ -212,17 +223,24 @@ private:
         AstNode* newlhsp = nullptr;  // nullptr = unlink old assign
         const AstSel* bitselp = nullptr;
         AstArraySel* arrayselp = nullptr;
+        AstVarRef* varrefp = nullptr;
         if (VN_IS(lhsp, Sel)) {
             bitselp = VN_AS(lhsp, Sel);
-            arrayselp = VN_AS(bitselp->fromp(), ArraySel);
-        } else {
+            arrayselp = VN_CAST(bitselp->fromp(), ArraySel);
+            if (!arrayselp) varrefp = VN_AS(bitselp->fromp(), VarRef);
+        } else if (VN_IS(lhsp, ArraySel)) {
             arrayselp = VN_AS(lhsp, ArraySel);
+        } else {
+            varrefp = VN_AS(lhsp, VarRef);
+        }
+        if (arrayselp) {
+            UASSERT_OBJ(!VN_IS(arrayselp->dtypep()->skipRefp(), UnpackArrayDType), nodep,
+                        "ArraySel with unpacked arrays should have been removed in V3Slice");
+            UINFO(4, "AssignDlyArray: " << nodep << endl);
+        } else {
+            UASSERT_OBJ(varrefp, nodep, "No arraysel nor varref");
+            UINFO(4, "AssignDlyOnSet: " << nodep << endl);
         }
-        UASSERT_OBJ(arrayselp, nodep, "No arraysel under bitsel?");
-        UASSERT_OBJ(!VN_IS(arrayselp->dtypep()->skipRefp(), UnpackArrayDType), nodep,
-                    "ArraySel with unpacked arrays should have been removed in V3Slice");
-        UINFO(4, "AssignDlyArray: " << nodep << endl);
-        //
         //=== Dimensions: __Vdlyvdim__
         std::deque dimvalp;  // Assignment value for each dimension of assignment
         AstNode* dimselp = arrayselp;
@@ -230,7 +248,7 @@ private:
             AstNode* const valp = VN_AS(dimselp, ArraySel)->bitp()->unlinkFrBack();
             dimvalp.push_front(valp);
         }
-        AstVarRef* const varrefp = VN_AS(dimselp, VarRef);
+        if (dimselp) varrefp = VN_AS(dimselp, VarRef);
         UASSERT_OBJ(varrefp, nodep, "No var underneath arraysels");
         UASSERT_OBJ(varrefp->varScopep(), varrefp, "Var didn't get varscoped in V3Scope.cpp");
         varrefp->unlinkFrBack();
@@ -293,7 +311,7 @@ private:
         AstVarScope* setvscp;
         AstAssignPre* setinitp = nullptr;
 
-        if (nodep->user3p()) {
+        if (!m_procp->isSuspendable() && nodep->user3p()) {
             // Simplistic optimization.  If the previous statement in same scope was also a =>,
             // then we told this nodep->user3 we can use its Vdlyvset rather than making a new one.
             // This is good for code like:
@@ -304,9 +322,12 @@ private:
             const string setvarname
                 = (string("__Vdlyvset__") + oldvarp->shortName() + "__v" + cvtToStr(modVecNum));
             setvscp = createVarSc(varrefp->varScopep(), setvarname, 1, nullptr);
-            setinitp = new AstAssignPre(nodep->fileline(),
-                                        new AstVarRef(nodep->fileline(), setvscp, VAccess::WRITE),
-                                        new AstConst(nodep->fileline(), 0));
+            if (!m_procp->isSuspendable()) {
+                // Suspendables reset __Vdlyvset__ in the AstAlwaysPost
+                setinitp = new AstAssignPre{
+                    nodep->fileline(), new AstVarRef{nodep->fileline(), setvscp, VAccess::WRITE},
+                    new AstConst{nodep->fileline(), 0}};
+            }
             AstAssign* const setassignp = new AstAssign(
                 nodep->fileline(), new AstVarRef(nodep->fileline(), setvscp, VAccess::WRITE),
                 new AstConst(nodep->fileline(), AstConst::BitTrue()));
@@ -332,19 +353,36 @@ private:
         // Build "IF (changeit) ...
         UINFO(9, "   For " << setvscp << endl);
         UINFO(9, "     & " << varrefp << endl);
-        AstAlwaysPost* finalp = VN_AS(varrefp->varScopep()->user4p(), AlwaysPost);
-        if (finalp) {
-            AstActive* const oldactivep = VN_AS(finalp->user2p(), Active);
-            checkActivePost(varrefp, oldactivep);
-            if (setinitp) oldactivep->addStmtsp(setinitp);
-        } else {  // first time we've dealt with this memory
-            finalp = new AstAlwaysPost(nodep->fileline(), nullptr /*sens*/, nullptr /*body*/);
-            UINFO(9, "     Created " << finalp << endl);
-            AstActive* const newactp = createActivePost(varrefp);
-            newactp->addStmtsp(finalp);
-            varrefp->varScopep()->user4p(finalp);
-            finalp->user2p(newactp);
-            if (setinitp) newactp->addStmtsp(setinitp);
+        AstAlwaysPost* finalp = nullptr;
+        if (m_procp->isSuspendable()) {
+            finalp = VN_AS(varrefp->varScopep()->user3p(), AlwaysPost);
+            if (!finalp) {
+                FileLine* const flp = nodep->fileline();
+                finalp = new AstAlwaysPost{flp, nullptr, nullptr};
+                UINFO(9, "     Created " << finalp << endl);
+                if (!m_procp->user3p()) {
+                    AstActive* const newactp = createActive(varrefp);
+                    m_procp->user3p(newactp);
+                    varrefp->varScopep()->user3p(finalp);
+                }
+                AstActive* const actp = VN_AS(m_procp->user3p(), Active);
+                actp->addStmtsp(finalp);
+            }
+        } else {
+            finalp = VN_AS(varrefp->varScopep()->user4p(), AlwaysPost);
+            if (finalp) {
+                AstActive* const oldactivep = VN_AS(finalp->user2p(), Active);
+                checkActivePost(varrefp, oldactivep);
+                if (setinitp) oldactivep->addStmtsp(setinitp);
+            } else {  // first time we've dealt with this memory
+                finalp = new AstAlwaysPost{nodep->fileline(), nullptr /*sens*/, nullptr /*body*/};
+                UINFO(9, "     Created " << finalp << endl);
+                AstActive* const newactp = createActive(varrefp);
+                newactp->addStmtsp(finalp);
+                varrefp->varScopep()->user4p(finalp);
+                finalp->user2p(newactp);
+                if (setinitp) newactp->addStmtsp(setinitp);
+            }
         }
         AstIf* postLogicp;
         if (finalp->user3p() == setvscp) {
@@ -362,6 +400,11 @@ private:
             finalp->user4p(postLogicp);  // and the associated IF, as we may be able to reuse it
         }
         postLogicp->addThensp(new AstAssign(nodep->fileline(), selectsp, valreadp));
+        if (m_procp->isSuspendable()) {
+            FileLine* const flp = nodep->fileline();
+            postLogicp->addThensp(new AstAssign{flp, new AstVarRef{flp, setvscp, VAccess::WRITE},
+                                                new AstConst{flp, 0}});
+        }
         return newlhsp;
     }
 
@@ -387,12 +430,82 @@ private:
         m_activep = nodep;
         VL_RESTORER(m_inInitial);
         {
-            m_inInitial = nodep->hasInitial();
+            AstSenTree* const senTreep = nodep->sensesp();
+            m_inInitial = senTreep->hasStatic() || senTreep->hasInitial();
             // Two sets to same variable in different actives must use different vars.
             AstNode::user3ClearTree();
             iterateChildren(nodep);
         }
     }
+    void visit(AstNodeProcedure* nodep) override {
+        m_procp = nodep;
+        m_timingDomains.clear();
+        iterateChildren(nodep);
+        m_procp = nullptr;
+        if (m_timingDomains.empty()) return;
+        if (auto* const actp = VN_AS(nodep->user3p(), Active)) {
+            // Merge all timing domains (and possibly the active's domain) to create a sentree for
+            // the post logic
+            // TODO: allow multiple sentrees per active, so we don't have to merge them and create
+            // a new trigger
+            auto* clockedDomain
+                = actp->sensesp()->hasClocked() ? actp->sensesp()->cloneTree(false) : nullptr;
+            for (auto* const domainp : m_timingDomains) {
+                if (!clockedDomain) {
+                    clockedDomain = domainp->cloneTree(false);
+                } else {
+                    clockedDomain->addSensesp(domainp->sensesp()->cloneTree(true));
+                    clockedDomain->multi(true);  // Comment that it was made from multiple domains
+                }
+            }
+            // We cannot constify the sentree using V3Const as user1-5 is already taken up by
+            // V3Delayed
+            actp->sensesp(clockedDomain);
+            actp->sensesStorep(clockedDomain);
+        }
+    }
+    void visit(AstCAwait* nodep) override { m_timingDomains.insert(nodep->sensesp()); }
+    void visit(AstFireEvent* nodep) override {
+        UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent");
+        FileLine* const flp = nodep->fileline();
+        if (nodep->isDelayed()) {
+            AstVarRef* const vrefp = VN_AS(nodep->operandp(), VarRef);
+            vrefp->unlinkFrBack();
+            const string newvarname = (string("__Vdly__") + vrefp->varp()->shortName());
+            AstVarScope* const dlyvscp = createVarSc(vrefp->varScopep(), newvarname, 1, nullptr);
+
+            const auto dlyRef = [=](VAccess access) {
+                return new AstVarRef{flp, dlyvscp, access};
+            };
+
+            AstAssignPre* const prep = new AstAssignPre{flp, dlyRef(VAccess::WRITE),
+                                                        new AstConst{flp, AstConst::BitFalse{}}};
+            AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr};
+            {
+                AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
+                postp->addStmtsp(ifp);
+                AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "fire"};
+                callp->statement(true);
+                callp->dtypeSetVoid();
+                ifp->addThensp(callp);
+            }
+
+            AstActive* const activep = createActive(nodep);
+            activep->addStmtsp(prep);
+            activep->addStmtsp(postp);
+
+            AstAssign* const assignp = new AstAssign{flp, dlyRef(VAccess::WRITE),
+                                                     new AstConst{flp, AstConst::BitTrue{}}};
+            nodep->replaceWith(assignp);
+        } else {
+            AstCMethodHard* const callp
+                = new AstCMethodHard{flp, nodep->operandp()->unlinkFrBack(), "fire"};
+            callp->dtypeSetVoid();
+            callp->statement(true);
+            nodep->replaceWith(callp);
+        }
+        nodep->deleteTree();
+    }
     void visit(AstAssignDly* nodep) override {
         m_inDly = true;
         m_nextDlyp
@@ -401,25 +514,28 @@ private:
             nodep->v3warn(E_UNSUPPORTED,
                           "Unsupported: Delayed assignment inside public function/task");
         }
-        if (VN_IS(nodep->lhsp(), ArraySel)
-            || (VN_IS(nodep->lhsp(), Sel)
-                && VN_IS(VN_AS(nodep->lhsp(), Sel)->fromp(), ArraySel))) {
-            AstNode* const lhsp = nodep->lhsp()->unlinkFrBack();
-            AstNode* const newlhsp = createDlyArray(nodep, lhsp);
-            if (m_inLoop) {
+        UASSERT_OBJ(m_procp, nodep, "Delayed assignment not under process");
+        const bool isArray = VN_IS(nodep->lhsp(), ArraySel)
+                             || (VN_IS(nodep->lhsp(), Sel)
+                                 && VN_IS(VN_AS(nodep->lhsp(), Sel)->fromp(), ArraySel));
+        if (m_procp->isSuspendable() || isArray) {
+            AstNode* const lhsp = nodep->lhsp();
+            AstNode* const newlhsp = createDlyOnSet(nodep, lhsp);
+            if (m_inLoop && isArray) {
                 nodep->v3warn(BLKLOOPINIT, "Unsupported: Delayed assignment to array inside for "
                                            "loops (non-delayed is ok - see docs)");
             }
             const AstBasicDType* const basicp = lhsp->dtypep()->basicp();
-            if (basicp && basicp->isEventValue()) {
+            if (basicp && basicp->isEvent()) {
                 nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays");
             }
             if (newlhsp) {
+                if (nodep->lhsp()) nodep->lhsp()->unlinkFrBack();
                 nodep->lhsp(newlhsp);
             } else {
                 VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
             }
-            VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
+            if (!lhsp->backp()) VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
         } else {
             iterateChildren(nodep);
         }
@@ -448,28 +564,18 @@ private:
                 if (!dlyvscp) {  // First use of this delayed variable
                     const string newvarname = (string("__Vdly__") + nodep->varp()->shortName());
                     dlyvscp = createVarSc(oldvscp, newvarname, 0, nullptr);
-                    AstNodeAssign* prep;
-                    const AstBasicDType* const basicp = oldvscp->dtypep()->basicp();
-                    if (basicp && basicp->isEventValue()) {
-                        // Events go to zero on next timestep unless reactivated
-                        prep = new AstAssignPre(
-                            nodep->fileline(),
-                            new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE),
-                            new AstConst(nodep->fileline(), AstConst::BitFalse()));
-                    } else {
-                        prep = new AstAssignPre(
-                            nodep->fileline(),
-                            new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE),
-                            new AstVarRef(nodep->fileline(), oldvscp, VAccess::READ));
-                    }
-                    AstNodeAssign* const postp = new AstAssignPost(
+                    AstNodeAssign* const prep = new AstAssignPre{
                         nodep->fileline(),
-                        new AstVarRef(nodep->fileline(), oldvscp, VAccess::WRITE),
-                        new AstVarRef(nodep->fileline(), dlyvscp, VAccess::READ));
+                        new AstVarRef{nodep->fileline(), dlyvscp, VAccess::WRITE},
+                        new AstVarRef{nodep->fileline(), oldvscp, VAccess::READ}};
+                    AstNodeAssign* const postp = new AstAssignPost{
+                        nodep->fileline(),
+                        new AstVarRef{nodep->fileline(), oldvscp, VAccess::WRITE},
+                        new AstVarRef{nodep->fileline(), dlyvscp, VAccess::READ}};
                     postp->lhsp()->user2(true);  // Don't detect this assignment
                     oldvscp->user1p(dlyvscp);  // So we can find it later
                     // Make new ACTIVE with identical sensitivity tree
-                    AstActive* const newactp = createActivePost(nodep);
+                    AstActive* const newactp = createActive(nodep);
                     dlyvscp->user2p(newactp);
                     newactp->addStmtsp(prep);  // Add to FRONT of statements
                     newactp->addStmtsp(postp);
diff --git a/src/V3Descope.cpp b/src/V3Descope.cpp
index b93c0b947..10ab9b2ff 100644
--- a/src/V3Descope.cpp
+++ b/src/V3Descope.cpp
@@ -186,10 +186,6 @@ private:
     }
 
     // VISITORS
-    void visit(AstNetlist* nodep) override {
-        nodep->dpiExportTriggerp(nullptr);
-        iterateChildren(nodep);
-    }
     void visit(AstNodeModule* nodep) override {
         VL_RESTORER(m_modp);
         {
diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp
new file mode 100644
index 000000000..9f08a3b12
--- /dev/null
+++ b/src/V3Dfg.cpp
@@ -0,0 +1,466 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Dfg.h"
+
+#include "V3File.h"
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+//------------------------------------------------------------------------------
+// DfgGraph
+//------------------------------------------------------------------------------
+
+DfgGraph::DfgGraph(AstModule& module, const string& name)
+    : m_modulep{&module}
+    , m_name{name} {}
+
+DfgGraph::~DfgGraph() {
+    forEachVertex([](DfgVertex& vtxp) { delete &vtxp; });
+}
+
+void DfgGraph::addGraph(DfgGraph& other) {
+    m_size += other.m_size;
+    other.m_size = 0;
+
+    const auto moveVertexList = [this](V3List& src, V3List& dst) {
+        if (DfgVertex* vtxp = src.begin()) {
+            vtxp->m_verticesEnt.moveAppend(src, dst, vtxp);
+            do {
+                vtxp->m_userCnt = 0;
+                vtxp->m_graphp = this;
+                vtxp = vtxp->verticesNext();
+            } while (vtxp);
+        }
+    };
+
+    moveVertexList(other.m_varVertices, m_varVertices);
+    moveVertexList(other.m_constVertices, m_constVertices);
+    moveVertexList(other.m_opVertices, m_opVertices);
+}
+
+static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
+
+// Dump one DfgVertex in Graphviz format
+static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
+
+    if (const DfgVarPacked* const varVtxp = vtx.cast()) {
+        AstVar* const varp = varVtxp->varp();
+        os << toDotId(vtx);
+        os << " [label=\"" << varp->name() << "\nW" << varVtxp->width() << " / F"
+           << varVtxp->fanout() << '"';
+
+        if (varp->direction() == VDirection::INPUT) {
+            os << ", shape=box, style=filled, fillcolor=chartreuse2";  // Green
+        } else if (varp->direction() == VDirection::OUTPUT) {
+            os << ", shape=box, style=filled, fillcolor=cyan2";  // Cyan
+        } else if (varp->direction() == VDirection::INOUT) {
+            os << ", shape=box, style=filled, fillcolor=darkorchid2";  // Purple
+        } else if (varVtxp->hasExtRefs()) {
+            os << ", shape=box, style=filled, fillcolor=firebrick2";  // Red
+        } else if (varVtxp->hasModRefs()) {
+            os << ", shape=box, style=filled, fillcolor=gold2";  // Yellow
+        } else if (varVtxp->keep()) {
+            os << ", shape=box, style=filled, fillcolor=grey";
+        } else {
+            os << ", shape=box";
+        }
+        os << "]" << endl;
+        return;
+    }
+
+    if (const DfgVarArray* const arrVtxp = vtx.cast()) {
+        AstVar* const varp = arrVtxp->varp();
+        const int elements = VN_AS(arrVtxp->dtypep(), UnpackArrayDType)->elementsConst();
+        os << toDotId(vtx);
+        os << " [label=\"" << varp->name() << "[" << elements << "]\"";
+        if (varp->direction() == VDirection::INPUT) {
+            os << ", shape=box3d, style=filled, fillcolor=chartreuse2";  // Green
+        } else if (varp->direction() == VDirection::OUTPUT) {
+            os << ", shape=box3d, style=filled, fillcolor=cyan2";  // Cyan
+        } else if (varp->direction() == VDirection::INOUT) {
+            os << ", shape=box3d, style=filled, fillcolor=darkorchid2";  // Purple
+        } else if (arrVtxp->hasExtRefs()) {
+            os << ", shape=box3d, style=filled, fillcolor=firebrick2";  // Red
+        } else if (arrVtxp->hasModRefs()) {
+            os << ", shape=box3d, style=filled, fillcolor=gold2";  // Yellow
+        } else if (arrVtxp->keep()) {
+            os << ", shape=box3d, style=filled, fillcolor=grey";
+        } else {
+            os << ", shape=box3d";
+        }
+        os << "]" << endl;
+        return;
+    }
+
+    if (const DfgConst* const constVtxp = vtx.cast()) {
+        const V3Number& num = constVtxp->num();
+
+        os << toDotId(vtx);
+        os << " [label=\"";
+        if (num.width() <= 32 && !num.isSigned()) {
+            os << constVtxp->width() << "'d" << num.toUInt() << "\n";
+            os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec;
+        } else {
+            os << num.ascii();
+        }
+        os << '"';
+        os << ", shape=plain";
+        os << "]" << endl;
+        return;
+    }
+
+    if (const DfgSel* const selVtxp = vtx.cast()) {
+        const uint32_t lsb = selVtxp->lsb();
+        const uint32_t msb = lsb + selVtxp->width() - 1;
+        os << toDotId(vtx);
+        os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F"
+           << vtx.fanout() << '"';
+        if (vtx.hasMultipleSinks()) {
+            os << ", shape=doublecircle";
+        } else {
+            os << ", shape=circle";
+        }
+        os << "]" << endl;
+        return;
+    }
+
+    os << toDotId(vtx);
+    os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() << '"';
+    if (vtx.hasMultipleSinks()) {
+        os << ", shape=doublecircle";
+    } else {
+        os << ", shape=circle";
+    }
+    os << "]" << endl;
+}
+
+// Dump one DfgEdge in Graphviz format
+static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& headlabel) {
+    os << toDotId(*edge.sourcep()) << " -> " << toDotId(*edge.sinkp());
+    if (!headlabel.empty()) os << " [headlabel=\"" << headlabel << "\"]";
+    os << endl;
+}
+
+// Dump one DfgVertex and all of its source DfgEdges in Graphviz format
+static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) {
+    dumpDotVertex(os, vtx);
+    vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {  //
+        if (edge.sourcep()) {
+            string headLabel;
+            if (vtx.arity() > 1 || vtx.is()) headLabel = vtx.srcName(idx);
+            dumpDotEdge(os, edge, headLabel);
+        }
+    });
+}
+
+void DfgGraph::dumpDot(std::ostream& os, const string& label) const {
+    // Header
+    os << "digraph dfg {" << endl;
+    os << "graph [label=\"" << name();
+    if (!label.empty()) os << "-" << label;
+    os << "\", labelloc=t, labeljust=l]" << endl;
+    os << "graph [rankdir=LR]" << endl;
+
+    // Emit all vertices
+    forEachVertex([&](const DfgVertex& vtx) { dumpDotVertexAndSourceEdges(os, vtx); });
+
+    // Footer
+    os << "}" << endl;
+}
+
+void DfgGraph::dumpDotFile(const string& fileName, const string& label) const {
+    // This generates a file used by graphviz, https://www.graphviz.org
+    // "hardcoded" parameters:
+    const std::unique_ptr os{V3File::new_ofstream(fileName)};
+    if (os->fail()) v3fatal("Cannot write to file: " << fileName);
+    dumpDot(*os.get(), label);
+    os->close();
+}
+
+void DfgGraph::dumpDotFilePrefixed(const string& label) const {
+    string fileName = name();
+    if (!label.empty()) fileName += "-" + label;
+    dumpDotFile(v3Global.debugFilename(fileName) + ".dot", label);
+}
+
+// Dump upstream logic cone starting from given vertex
+static void dumpDotUpstreamConeFromVertex(std::ostream& os, const DfgVertex& vtx) {
+    // Work queue for depth first traversal starting from this vertex
+    std::vector queue{&vtx};
+
+    // Set of already visited vertices
+    std::unordered_set visited;
+
+    // Depth first traversal
+    while (!queue.empty()) {
+        // Pop next work item
+        const DfgVertex* const itemp = queue.back();
+        queue.pop_back();
+
+        // Mark vertex as visited
+        const bool isFirstEncounter = visited.insert(itemp).second;
+
+        // If we have already visited this vertex during the traversal, then move on.
+        if (!isFirstEncounter) continue;
+
+        // Enqueue all sources of this vertex.
+        itemp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); });
+
+        // Emit this vertex and all of its source edges
+        dumpDotVertexAndSourceEdges(os, *itemp);
+    }
+
+    // Emit all DfgVarPacked vertices that have external references driven by this vertex
+    vtx.forEachSink([&](const DfgVertex& dst) {
+        if (const DfgVarPacked* const varVtxp = dst.cast()) {
+            if (varVtxp->hasRefs()) dumpDotVertexAndSourceEdges(os, dst);
+        }
+    });
+}
+
+// LCOV_EXCL_START // Debug function for developer use only
+void DfgGraph::dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx,
+                                   const string& name) const {
+    // Open output file
+    const std::unique_ptr os{V3File::new_ofstream(fileName)};
+    if (os->fail()) v3fatal("Cannot write to file: " << fileName);
+
+    // Header
+    *os << "digraph dfg {" << endl;
+    if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]" << endl;
+    *os << "graph [rankdir=LR]" << endl;
+
+    // Dump the cone
+    dumpDotUpstreamConeFromVertex(*os, vtx);
+
+    // Footer
+    *os << "}" << endl;
+
+    // Done
+    os->close();
+}
+// LCOV_EXCL_STOP
+
+void DfgGraph::dumpDotAllVarConesPrefixed(const string& label) const {
+    const string prefix = label.empty() ? name() + "-cone-" : name() + "-" + label + "-cone-";
+    forEachVertex([&](const DfgVertex& vtx) {
+        // Check if this vertex drives a variable referenced outside the DFG.
+        const DfgVarPacked* const sinkp
+            = vtx.findSink([](const DfgVarPacked& sink) {  //
+                  return sink.hasRefs();
+              });
+
+        // We only dump cones driving an externally referenced variable
+        if (!sinkp) return;
+
+        // Open output file
+        const string coneName{prefix + sinkp->varp()->name()};
+        const string fileName{v3Global.debugFilename(coneName) + ".dot"};
+        const std::unique_ptr os{V3File::new_ofstream(fileName)};
+        if (os->fail()) v3fatal("Cannot write to file: " << fileName);
+
+        // Header
+        *os << "digraph dfg {" << endl;
+        *os << "graph [label=\"" << coneName << "\", labelloc=t, labeljust=l]" << endl;
+        *os << "graph [rankdir=LR]" << endl;
+
+        // Dump this cone
+        dumpDotUpstreamConeFromVertex(*os, vtx);
+
+        // Footer
+        *os << "}" << endl;
+
+        // Done with this logic cone
+        os->close();
+    });
+}
+
+//------------------------------------------------------------------------------
+// DfgEdge
+//------------------------------------------------------------------------------
+
+void DfgEdge::unlinkSource() {
+    if (!m_sourcep) return;
+#ifdef VL_DEBUG
+    {
+        DfgEdge* sinkp = m_sourcep->m_sinksp;
+        while (sinkp) {
+            if (sinkp == this) break;
+            sinkp = sinkp->m_nextp;
+        }
+        UASSERT(sinkp, "'m_sourcep' does not have this edge as sink");
+    }
+#endif
+    // Relink pointers of predecessor and successor
+    if (m_prevp) m_prevp->m_nextp = m_nextp;
+    if (m_nextp) m_nextp->m_prevp = m_prevp;
+    // If head of list in source, update source's head pointer
+    if (m_sourcep->m_sinksp == this) m_sourcep->m_sinksp = m_nextp;
+    // Mark source as unconnected
+    m_sourcep = nullptr;
+    // Clear links. This is not strictly necessary, but might catch bugs.
+    m_prevp = nullptr;
+    m_nextp = nullptr;
+}
+
+void DfgEdge::relinkSource(DfgVertex* newSourcep) {
+    // Unlink current source, if any
+    unlinkSource();
+    // Link new source
+    m_sourcep = newSourcep;
+    // Prepend to sink list in source
+    m_nextp = newSourcep->m_sinksp;
+    if (m_nextp) m_nextp->m_prevp = this;
+    newSourcep->m_sinksp = this;
+}
+
+//------------------------------------------------------------------------------
+// DfgVertex
+//------------------------------------------------------------------------------
+
+DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
+    : m_filelinep{flp}
+    , m_dtypep{dtypep}
+    , m_type{type} {
+    dfg.addVertex(*this);
+}
+
+DfgVertex::~DfgVertex() {
+    // TODO: It would be best to intern these via AstTypeTable to save the effort
+    if (VN_IS(m_dtypep, UnpackArrayDType)) VL_DO_DANGLING(delete m_dtypep, m_dtypep);
+}
+
+bool DfgVertex::selfEquals(const DfgVertex& that) const { return true; }
+
+V3Hash DfgVertex::selfHash() const { return V3Hash{}; }
+
+bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const {
+    if (this == &that) return true;
+    if (this->type() != that.type()) return false;
+    if (this->dtypep() != that.dtypep()) return false;
+    if (!this->selfEquals(that)) return false;
+
+    const auto key = (this < &that) ? EqualsCache::key_type{this, &that}  //
+                                    : EqualsCache::key_type{&that, this};
+    // Note: the recursive invocation can cause a re-hash of the cache which invalidates iterators
+    uint8_t result = cache[key];
+    if (!result) {
+        result = 2;  // Assume equals
+        auto thisPair = this->sourceEdges();
+        const DfgEdge* const thisSrcEdgesp = thisPair.first;
+        const size_t thisArity = thisPair.second;
+        auto thatPair = that.sourceEdges();
+        const DfgEdge* const thatSrcEdgesp = thatPair.first;
+        const size_t thatArity = thatPair.second;
+        UASSERT_OBJ(thisArity == thatArity, this, "Same type vertices must have same arity!");
+        for (size_t i = 0; i < thisArity; ++i) {
+            const DfgVertex* const thisSrcVtxp = thisSrcEdgesp[i].m_sourcep;
+            const DfgVertex* const thatSrcVtxp = thatSrcEdgesp[i].m_sourcep;
+            if (thisSrcVtxp == thatSrcVtxp) continue;
+            if (!thisSrcVtxp || !thatSrcVtxp || !thisSrcVtxp->equals(*thatSrcVtxp, cache)) {
+                result = 1;  // Mark not equal
+                break;
+            }
+        }
+        cache[key] = result;
+    }
+    return result >> 1;
+}
+
+V3Hash DfgVertex::hash() {
+    V3Hash& result = user();
+    if (!result.value()) {
+        V3Hash hash{selfHash()};
+        // Variables are defined by themselves, so there is no need to hash them further
+        // (especially the sources). This enables sound hashing of graphs circular only through
+        // variables, which we rely on.
+        if (!is()) {
+            hash += m_type;
+            hash += width();  // Currently all non-variable vertices are packed, so this is safe
+            const auto pair = sourceEdges();
+            const DfgEdge* const edgesp = pair.first;
+            const size_t arity = pair.second;
+            // Sources must always be connected in well-formed graphs
+            for (size_t i = 0; i < arity; ++i) hash += edgesp[i].m_sourcep->hash();
+        }
+        result = hash;
+    }
+    return result;
+}
+
+uint32_t DfgVertex::fanout() const {
+    uint32_t result = 0;
+    forEachSinkEdge([&](const DfgEdge&) { ++result; });
+    return result;
+}
+
+void DfgVertex::unlinkDelete(DfgGraph& dfg) {
+    // Unlink source edges
+    forEachSourceEdge([](DfgEdge& edge, size_t) { edge.unlinkSource(); });
+    // Unlink sink edges
+    forEachSinkEdge([](DfgEdge& edge) { edge.unlinkSource(); });
+    // Remove from graph
+    dfg.removeVertex(*this);
+    // Delete
+    delete this;
+}
+
+void DfgVertex::replaceWith(DfgVertex* newSorucep) {
+    while (m_sinksp) m_sinksp->relinkSource(newSorucep);
+}
+
+//------------------------------------------------------------------------------
+// Vertex classes
+//------------------------------------------------------------------------------
+
+// DfgConst ----------
+
+bool DfgConst::selfEquals(const DfgVertex& that) const {
+    return num().isCaseEq(that.as()->num());
+}
+
+V3Hash DfgConst::selfHash() const { return num().toHash(); }
+
+// DfgSel ----------
+
+bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as()->lsb(); }
+
+V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
+
+// DfgVertexVar ----------
+
+bool DfgVertexVar::selfEquals(const DfgVertex& that) const {
+    UASSERT_OBJ(varp() != that.as()->varp(), this,
+                "There should only be one DfgVertexVar for a given AstVar");
+    return false;
+}
+
+V3Hash DfgVertexVar::selfHash() const {
+    V3Hash hash;
+    hash += m_varp->name();
+    hash += m_varp->varType();
+    return hash;
+}
+
+//------------------------------------------------------------------------------
+// DfgVisitor
+//------------------------------------------------------------------------------
+
+#include "V3Dfg__gen_visitor_defns.h"  // From ./astgen
diff --git a/src/V3Dfg.h b/src/V3Dfg.h
new file mode 100644
index 000000000..a81f0cea5
--- /dev/null
+++ b/src/V3Dfg.h
@@ -0,0 +1,901 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// This is a data-flow graph based representation of combinational logic,
+// the main difference from a V3Graph is that DfgVertex owns the storage
+// of it's input edges (operands/sources/arguments), and can access each
+// input edge directly by indexing, making modifications more efficient
+// than the linked list based structures used by V3Graph.
+//
+// A bulk of the DfgVertex sub-types are generated by astgen, and are
+// analogous to the corresponding AstNode sub-types.
+//
+// See also the internals documentation docs/internals.rst
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3DFG_H_
+#define VERILATOR_V3DFG_H_
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+#include "V3Error.h"
+#include "V3Hash.h"
+#include "V3List.h"
+
+#include "V3Dfg__gen_forward_class_decls.h"  // From ./astgen
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifndef VL_NOT_FINAL
+#define VL_NOT_FINAL  // This #define fixes broken code folding in the CLion IDE
+#endif
+
+class DfgEdge;
+class DfgVisitor;
+
+//------------------------------------------------------------------------------
+
+// Specialization of std::hash for a std::pair for use below
+template <>
+struct std::hash> final {
+    size_t operator()(const std::pair& item) const {
+        const size_t a = reinterpret_cast(item.first);
+        const size_t b = reinterpret_cast(item.second);
+        constexpr size_t halfWidth = 8 * sizeof(b) / 2;
+        return a ^ ((b << halfWidth) | (b >> halfWidth));
+    }
+};
+
+//------------------------------------------------------------------------------
+// Dataflow vertex type enum
+//------------------------------------------------------------------------------
+
+class VDfgType final {
+public:
+#include "V3Dfg__gen_type_enum.h"  // From ./astgen
+    enum en m_e;
+    VDfgType() = default;
+    // cppcheck-suppress noExplicitConstructor
+    constexpr VDfgType(en _e)
+        : m_e{_e} {}
+    constexpr operator en() const { return m_e; }
+};
+constexpr bool operator==(VDfgType lhs, VDfgType rhs) { return lhs.m_e == rhs.m_e; }
+constexpr bool operator==(VDfgType lhs, VDfgType::en rhs) { return lhs.m_e == rhs; }
+constexpr bool operator==(VDfgType::en lhs, VDfgType rhs) { return lhs == rhs.m_e; }
+inline std::ostream& operator<<(std::ostream& os, const VDfgType& t) { return os << t.ascii(); }
+
+//------------------------------------------------------------------------------
+// Dataflow graph
+//------------------------------------------------------------------------------
+
+class DfgGraph final {
+    friend class DfgVertex;
+
+    // TYPES
+
+    // RAII handle for DfgVertex user data
+    class UserDataInUse final {
+        DfgGraph* m_graphp;  // The referenced graph
+
+    public:
+        UserDataInUse(DfgGraph* graphp)
+            : m_graphp{graphp} {}
+        VL_UNCOPYABLE(UserDataInUse);
+        UserDataInUse(UserDataInUse&& that) {
+            UASSERT(that.m_graphp, "Moving from empty");
+            m_graphp = vlstd::exchange(that.m_graphp, nullptr);
+        }
+        UserDataInUse& operator=(UserDataInUse&& that) {
+            UASSERT(that.m_graphp, "Moving from empty");
+            m_graphp = vlstd::exchange(that.m_graphp, nullptr);
+            return *this;
+        }
+
+        ~UserDataInUse() {
+            if (m_graphp) m_graphp->m_userCurrent = 0;
+        }
+    };
+
+    // MEMBERS
+
+    // Variables and constants make up a significant proportion of vertices (40-50% was observed
+    // in large designs), and they can often be treated specially in algorithms, which in turn
+    // enables significant verilation performance gains, so we keep these in separate lists for
+    // direct access.
+    V3List m_varVertices;  // The variable vertices in the graph
+    V3List m_constVertices;  // The constant vertices in the graph
+    V3List m_opVertices;  // The operation vertices in the graph
+
+    size_t m_size = 0;  // Number of vertices in the graph
+    uint32_t m_userCurrent = 0;  // Vertex user data generation number currently in use
+    uint32_t m_userCnt = 0;  // Vertex user data generation counter
+    // Parent of the graph (i.e.: the module containing the logic represented by this graph).
+    AstModule* const m_modulep;
+    const string m_name;  // Name of graph (for debugging)
+
+public:
+    // CONSTRUCTOR
+    explicit DfgGraph(AstModule& module, const string& name = "");
+    ~DfgGraph();
+    VL_UNCOPYABLE(DfgGraph);
+
+    // METHODS
+public:
+    // Add DfgVertex to this graph (assumes not yet contained).
+    inline void addVertex(DfgVertex& vtx);
+    // Remove DfgVertex form this graph (assumes it is contained).
+    inline void removeVertex(DfgVertex& vtx);
+    // Number of vertices in this graph
+    size_t size() const { return m_size; }
+    // Parent module
+    AstModule* modulep() const { return m_modulep; }
+    // Name of this graph
+    const string& name() const { return m_name; }
+
+    // Reset Vertex user data
+    UserDataInUse userDataInUse() {
+        UASSERT(!m_userCurrent, "Conflicting use of DfgVertex user data");
+        ++m_userCnt;
+        UASSERT(m_userCnt, "'m_userCnt' overflow");
+        m_userCurrent = m_userCnt;
+        return UserDataInUse{this};
+    }
+
+    // Access to vertex lists for faster iteration in important contexts
+    inline DfgVertexVar* varVerticesBeginp() const;
+    inline DfgVertexVar* varVerticesRbeginp() const;
+    inline DfgConst* constVerticesBeginp() const;
+    inline DfgConst* constVerticesRbeginp() const;
+    inline DfgVertex* opVerticesBeginp() const;
+    inline DfgVertex* opVerticesRbeginp() const;
+
+    // Calls given function 'f' for each vertex in the graph. It is safe to manipulate any vertices
+    // in the graph, or to delete/unlink the vertex passed to 'f' during iteration. It is however
+    // not safe to delete/unlink any vertex in the same graph other than the one passed to 'f'.
+    inline void forEachVertex(std::function f);
+
+    // 'const' variant of 'forEachVertex'. No mutation allowed.
+    inline void forEachVertex(std::function f) const;
+
+    // Add contents of other graph to this graph. Leaves other graph empty.
+    void addGraph(DfgGraph& other);
+
+    // Split this graph into individual components (unique sub-graphs with no edges between them).
+    // Also removes any vertices that are not weakly connected to any variable.
+    // Leaves 'this' graph empty.
+    std::vector> splitIntoComponents(std::string label);
+
+    // Extract cyclic sub-graphs from 'this' graph. Cyclic sub-graphs are those that contain at
+    // least one strongly connected component (SCC) plus any other vertices that feed or sink from
+    // the SCCs, up to a variable boundary. This means that the returned graphs are guaranteed to
+    // be cyclic, but they are not guaranteed to be strongly connected (however, they are always
+    // at least weakly connected). Trivial SCCs that are acyclic (i.e.: vertices that are not part
+    // of a cycle) are left in 'this' graph. This means that at the end 'this' graph is guaranteed
+    // to be a DAG (acyclic). 'this' will not necessarily be a connected graph at the end, even if
+    // it was originally connected.
+    std::vector> extractCyclicComponents(std::string label);
+
+    // Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of
+    // the graph which is included in the output.
+    void dumpDot(std::ostream& os, const string& label = "") const;
+    // Dump graph in Graphviz format into a new file with the given 'fileName'. 'label' is added to
+    // the name of the graph which is included in the output.
+    void dumpDotFile(const string& fileName, const string& label = "") const;
+    // Dump graph in Graphviz format into a new automatically numbered debug file. 'label' is
+    // added to the name of the graph, which is included in the file name and the output.
+    void dumpDotFilePrefixed(const string& label = "") const;
+    // Dump upstream (source) logic cone starting from given vertex into a file with the given
+    // 'fileName'. 'name' is the name of the graph, which is included in the output.
+    void dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx,
+                             const string& name = "") const;
+    // Dump all individual logic cones driving external variables in Graphviz format into separate
+    // new automatically numbered debug files. 'label' is added to the name of the graph, which is
+    // included in the file names and the output. This is useful for very large graphs that are
+    // otherwise difficult to browse visually due to their size.
+    void dumpDotAllVarConesPrefixed(const string& label = "") const;
+};
+
+//------------------------------------------------------------------------------
+// Dataflow graph edge
+//------------------------------------------------------------------------------
+
+class DfgEdge final {
+    friend class DfgVertex;
+
+    DfgEdge* m_nextp = nullptr;  // Next edge in sink list
+    DfgEdge* m_prevp = nullptr;  // Previous edge in sink list
+    DfgVertex* m_sourcep = nullptr;  // The source vertex driving this edge
+    // Note that the sink vertex owns the edge, so it is immutable, but because we want to be able
+    // to allocate these as arrays, we use a default constructor + 'init' method to set m_sinkp.
+    DfgVertex* const m_sinkp = nullptr;  // The sink vertex
+
+public:
+    DfgEdge() {}
+    void init(DfgVertex* sinkp) { const_cast(m_sinkp) = sinkp; }
+
+    // The source (driver) of this edge
+    DfgVertex* sourcep() const { return m_sourcep; }
+    // The sink (consumer) of this edge
+    DfgVertex* sinkp() const { return m_sinkp; }
+    // Remove driver of this edge
+    void unlinkSource();
+    // Relink this edge to be driven from the given new source vertex
+    void relinkSource(DfgVertex* newSourcep);
+};
+
+//------------------------------------------------------------------------------
+// Dataflow graph vertex
+//------------------------------------------------------------------------------
+
+// Base data flow graph vertex
+class DfgVertex VL_NOT_FINAL {
+    friend class DfgGraph;
+    friend class DfgEdge;
+    friend class DfgVisitor;
+
+    using UserDataStorage = void*;  // Storage allocated for user data
+
+    // STATE
+    V3ListEnt m_verticesEnt;  // V3List handle of this vertex, kept under the DfgGraph
+protected:
+    DfgEdge* m_sinksp = nullptr;  // List of sinks of this vertex
+    FileLine* const m_filelinep;  // Source location
+    AstNodeDType* m_dtypep;  // Data type of the result of this vertex - mutable for efficiency
+    DfgGraph* m_graphp;  // The containing DfgGraph
+    const VDfgType m_type;  // Vertex type tag
+    uint32_t m_userCnt = 0;  // User data generation number
+    UserDataStorage m_userDataStorage;  // User data storage
+
+    // CONSTRUCTOR
+    DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep);
+
+public:
+    virtual ~DfgVertex();
+
+    // METHODS
+private:
+    // Visitor accept method
+    virtual void accept(DfgVisitor& v) = 0;
+
+    // Part of Vertex equality only dependent on this vertex
+    virtual bool selfEquals(const DfgVertex& that) const;
+
+    // Part of Vertex hash only dependent on this vertex
+    virtual V3Hash selfHash() const;
+
+public:
+    // Supported packed types
+    static bool isSupportedPackedDType(const AstNodeDType* dtypep) {
+        dtypep = dtypep->skipRefp();
+        if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
+            return typep->keyword().isIntNumeric();
+        }
+        if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
+            return isSupportedPackedDType(typep->subDTypep());
+        }
+        if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
+            return typep->packed();
+        }
+        return false;
+    }
+
+    // Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex
+    static bool isSupportedDType(const AstNodeDType* dtypep) {
+        dtypep = dtypep->skipRefp();
+        // Support unpacked arrays of packed types
+        if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
+            return isSupportedPackedDType(typep->subDTypep());
+        }
+        // Support packed types
+        return isSupportedPackedDType(dtypep);
+    }
+
+    // Return data type used to represent any packed value of the given 'width'. All packed types
+    // of a given width use the same canonical data type, as the only interesting information is
+    // the total width.
+    static AstNodeDType* dtypeForWidth(uint32_t width) {
+        return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED);
+    }
+
+    // Return data type used to represent the type of 'nodep' when converted to a DfgVertex
+    static AstNodeDType* dtypeFor(const AstNode* nodep) {
+        UDEBUGONLY(UASSERT_OBJ(isSupportedDType(nodep->dtypep()), nodep, "Unsupported dtype"););
+        // For simplicity, all packed types are represented with a fixed type
+        if (AstUnpackArrayDType* const typep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) {
+            // TODO: these need interning via AstTypeTable otherwise they leak
+            return new AstUnpackArrayDType{typep->fileline(),
+                                           dtypeForWidth(typep->subDTypep()->width()),
+                                           typep->rangep()->cloneTree(false)};
+        }
+        return dtypeForWidth(nodep->width());
+    }
+
+    // Source location
+    FileLine* fileline() const { return m_filelinep; }
+    // The data type of the result of the nodes
+    AstNodeDType* dtypep() const { return m_dtypep; }
+    void dtypep(AstNodeDType* nodep) { m_dtypep = nodep; }
+    // The type of this vertex
+    VDfgType type() const { return m_type; }
+
+    // Retrieve user data, constructing it fresh on first try.
+    template 
+    T& user() {
+        static_assert(sizeof(T) <= sizeof(UserDataStorage),
+                      "Size of user data type 'T' is too large for allocated storage");
+        static_assert(alignof(T) <= alignof(UserDataStorage),
+                      "Alignment of user data type 'T' is larger than allocated storage");
+        T* const storagep = reinterpret_cast(&m_userDataStorage);
+        const uint32_t userCurrent = m_graphp->m_userCurrent;
+        UDEBUGONLY(UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving"););
+        if (m_userCnt != userCurrent) {
+            m_userCnt = userCurrent;
+            VL_ATTR_UNUSED T* const resultp = new (storagep) T{};
+            UDEBUGONLY(UASSERT_OBJ(resultp == storagep, this, "Something is odd"););
+        }
+        return *storagep;
+    }
+
+    // Retrieve user data, must be current.
+    template 
+    T& getUser() {
+        static_assert(sizeof(T) <= sizeof(UserDataStorage),
+                      "Size of user data type 'T' is too large for allocated storage");
+        static_assert(alignof(T) <= alignof(UserDataStorage),
+                      "Alignment of user data type 'T' is larger than allocated storage");
+        T* const storagep = reinterpret_cast(&m_userDataStorage);
+#if VL_DEBUG
+        const uint32_t userCurrent = m_graphp->m_userCurrent;
+        UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving");
+        UASSERT_OBJ(m_userCnt == userCurrent, this, "DfgVertex user data is stale");
+#endif
+        return *storagep;
+    }
+
+    // Width of result
+    uint32_t width() const {
+        // This is a hot enough function that this is an expensive check, so in debug build only.
+        UDEBUGONLY(UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "non-packed has no 'width()'"););
+        return dtypep()->width();
+    }
+
+    // Cache type for 'equals' below
+    using EqualsCache = std::unordered_map, uint8_t>;
+
+    // Vertex equality (based on this vertex and all upstream vertices feeding into this vertex).
+    // Returns true, if the vertices can be substituted for each other without changing the
+    // semantics of the logic. The 'cache' argument is used to store results to avoid repeat
+    // evaluations, but it requires that the upstream sources of the compared vertices do not
+    // change between invocations.
+    bool equals(const DfgVertex& that, EqualsCache& cache) const;
+
+    // Uncached version of 'equals'
+    bool equals(const DfgVertex& that) const {
+        EqualsCache cache;  // Still cache recursive calls within this invocation
+        return equals(that, cache);
+    }
+
+    // Hash of vertex (depends on this vertex and all upstream vertices feeding into this vertex).
+    // Uses user data for caching hashes
+    V3Hash hash();
+
+    // Source edges of this vertex
+    virtual std::pair sourceEdges() = 0;
+
+    // Source edges of this vertex
+    virtual std::pair sourceEdges() const = 0;
+
+    // Arity (number of sources) of this vertex
+    size_t arity() const { return sourceEdges().second; }
+
+    // Predicate: has 1 or more sinks
+    bool hasSinks() const { return m_sinksp != nullptr; }
+
+    // Predicate: has 2 or more sinks
+    bool hasMultipleSinks() const { return m_sinksp && m_sinksp->m_nextp; }
+
+    // Fanout (number of sinks) of this vertex (expensive to compute)
+    uint32_t fanout() const;
+
+    // Unlink from container (graph or builder), then delete this vertex
+    void unlinkDelete(DfgGraph& dfg);
+
+    // Relink all sinks to be driven from the given new source
+    void replaceWith(DfgVertex* newSourcep);
+
+    // Access to vertex list for faster iteration in important contexts
+    DfgVertex* verticesNext() const { return m_verticesEnt.nextp(); }
+    DfgVertex* verticesPrev() const { return m_verticesEnt.prevp(); }
+
+    // Calls given function 'f' for each source vertex of this vertex
+    // Unconnected source edges are not iterated.
+    inline void forEachSource(std::function f);
+
+    // Calls given function 'f' for each source vertex of this vertex
+    // Unconnected source edges are not iterated.
+    inline void forEachSource(std::function f) const;
+
+    // Calls given function 'f' for each source edge of this vertex. Also passes source index.
+    inline void forEachSourceEdge(std::function f);
+
+    // Calls given function 'f' for each source edge of this vertex. Also passes source index.
+    inline void forEachSourceEdge(std::function f) const;
+
+    // Calls given function 'f' for each sink vertex of this vertex
+    // Unlinking/deleting the given sink during iteration is safe, but not other sinks of this
+    // vertex.
+    inline void forEachSink(std::function f);
+
+    // Calls given function 'f' for each sink vertex of this vertex
+    inline void forEachSink(std::function f) const;
+
+    // Calls given function 'f' for each sink edge of this vertex.
+    // Unlinking/deleting the given sink during iteration is safe, but not other sinks of this
+    // vertex.
+    inline void forEachSinkEdge(std::function f);
+
+    // Calls given function 'f' for each sink edge of this vertex.
+    inline void forEachSinkEdge(std::function f) const;
+
+    // Returns first source edge which satisfies the given predicate 'p', or nullptr if no such
+    // sink vertex exists
+    inline const DfgEdge* findSourceEdge(std::function p) const;
+
+    // Returns first sink vertex of type 'Vertex' which satisfies the given predicate 'p',
+    // or nullptr if no such sink vertex exists
+    template 
+    inline Vertex* findSink(std::function p) const;
+
+    // Returns first sink vertex of type 'Vertex', or nullptr if no such sink vertex exists.
+    // This is a special case of 'findSink' above with the predicate always true.
+    template 
+    inline Vertex* findSink() const;
+
+    // Is this a DfgConst that is all zeroes
+    inline bool isZero() const;
+
+    // Is this a DfgConst that is all ones
+    inline bool isOnes() const;
+
+    // Methods that allow DfgVertex to participate in error reporting/messaging
+    void v3errorEnd(std::ostringstream& str) const { m_filelinep->v3errorEnd(str); }
+    void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN {
+        m_filelinep->v3errorEndFatal(str);
+    }
+    string warnContextPrimary() const { return fileline()->warnContextPrimary(); }
+    string warnContextSecondary() const { return fileline()->warnContextSecondary(); }
+    string warnMore() const { return fileline()->warnMore(); }
+    string warnOther() const { return fileline()->warnOther(); }
+
+private:
+    // For internal use only.
+    // Note: specializations for particular vertex types are provided by 'astgen'
+    template 
+    inline static bool privateTypeTest(const DfgVertex* nodep);
+
+public:
+    // Subtype test
+    template 
+    bool is() const {
+        static_assert(std::is_base_of::value, "'T' must be a subtype of DfgVertex");
+        return privateTypeTest::type>(this);
+    }
+
+    // Ensure subtype, then cast to that type
+    template 
+    T* as() {
+        UASSERT_OBJ(is(), this,
+                    "DfgVertex is not of expected type, but instead has type '" << typeName()
+                                                                                << "'");
+        return static_cast(this);
+    }
+    template 
+    const T* as() const {
+        UASSERT_OBJ(is(), this,
+                    "DfgVertex is not of expected type, but instead has type '" << typeName()
+                                                                                << "'");
+        return static_cast(this);
+    }
+
+    // Cast to subtype, or null if different
+    template 
+    T* cast() {
+        return is() ? static_cast(this) : nullptr;
+    }
+    template 
+    const T* cast() const {
+        return is() ? static_cast(this) : nullptr;
+    }
+
+    // Human-readable vertex type as string for debugging
+    const string typeName() const { return m_type.ascii(); }
+
+    // Human-readable name for source operand with given index for debugging
+    virtual const string srcName(size_t idx) const = 0;
+};
+
+// Specialisations of privateTypeTest
+#include "V3Dfg__gen_type_tests.h"  // From ./astgen
+
+//------------------------------------------------------------------------------
+// Dfg vertex visitor
+//------------------------------------------------------------------------------
+
+class DfgVisitor VL_NOT_FINAL {
+public:
+    // Dispatch to most specific 'visit' method on 'vtxp'
+    void iterate(DfgVertex* vtxp) { vtxp->accept(*this); }
+
+    virtual void visit(DfgVertex* nodep) = 0;
+#include "V3Dfg__gen_visitor_decls.h"  // From ./astgen
+};
+
+//------------------------------------------------------------------------------
+// Inline method definitions
+//------------------------------------------------------------------------------
+
+void DfgGraph::addVertex(DfgVertex& vtx) {
+    // Note: changes here need to be replicated in DfgGraph::addGraph
+    ++m_size;
+    if (vtx.is()) {
+        vtx.m_verticesEnt.pushBack(m_constVertices, &vtx);
+    } else if (vtx.is()) {
+        vtx.m_verticesEnt.pushBack(m_varVertices, &vtx);
+    } else {
+        vtx.m_verticesEnt.pushBack(m_opVertices, &vtx);
+    }
+    vtx.m_userCnt = 0;
+    vtx.m_graphp = this;
+}
+
+void DfgGraph::removeVertex(DfgVertex& vtx) {
+    // Note: changes here need to be replicated in DfgGraph::addGraph
+    --m_size;
+    if (vtx.is()) {
+        vtx.m_verticesEnt.unlink(m_constVertices, &vtx);
+    } else if (vtx.is()) {
+        vtx.m_verticesEnt.unlink(m_varVertices, &vtx);
+    } else {
+        vtx.m_verticesEnt.unlink(m_opVertices, &vtx);
+    }
+    vtx.m_userCnt = 0;
+    vtx.m_graphp = nullptr;
+}
+
+void DfgGraph::forEachVertex(std::function f) {
+    for (DfgVertex *vtxp = m_varVertices.begin(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        f(*vtxp);
+    }
+    for (DfgVertex *vtxp = m_constVertices.begin(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        f(*vtxp);
+    }
+    for (DfgVertex *vtxp = m_opVertices.begin(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        f(*vtxp);
+    }
+}
+
+void DfgGraph::forEachVertex(std::function f) const {
+    for (const DfgVertex* vtxp = m_varVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) {
+        f(*vtxp);
+    }
+    for (const DfgVertex* vtxp = m_constVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) {
+        f(*vtxp);
+    }
+    for (const DfgVertex* vtxp = m_opVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) {
+        f(*vtxp);
+    }
+}
+
+void DfgVertex::forEachSource(std::function f) {
+    const auto pair = sourceEdges();
+    const DfgEdge* const edgesp = pair.first;
+    const size_t arity = pair.second;
+    for (size_t i = 0; i < arity; ++i) {
+        if (DfgVertex* const sourcep = edgesp[i].m_sourcep) f(*sourcep);
+    }
+}
+
+void DfgVertex::forEachSource(std::function f) const {
+    const auto pair = sourceEdges();
+    const DfgEdge* const edgesp = pair.first;
+    const size_t arity = pair.second;
+    for (size_t i = 0; i < arity; ++i) {
+        if (DfgVertex* const sourcep = edgesp[i].m_sourcep) f(*sourcep);
+    }
+}
+
+void DfgVertex::forEachSink(std::function f) {
+    for (const DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) {
+        nextp = edgep->m_nextp;
+        f(*edgep->m_sinkp);
+    }
+}
+
+void DfgVertex::forEachSink(std::function f) const {
+    for (const DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) f(*edgep->m_sinkp);
+}
+
+void DfgVertex::forEachSourceEdge(std::function f) {
+    const auto pair = sourceEdges();
+    DfgEdge* const edgesp = pair.first;
+    const size_t arity = pair.second;
+    for (size_t i = 0; i < arity; ++i) f(edgesp[i], i);
+}
+
+void DfgVertex::forEachSourceEdge(std::function f) const {
+    const auto pair = sourceEdges();
+    const DfgEdge* const edgesp = pair.first;
+    const size_t arity = pair.second;
+    for (size_t i = 0; i < arity; ++i) f(edgesp[i], i);
+}
+
+void DfgVertex::forEachSinkEdge(std::function f) {
+    for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) {
+        nextp = edgep->m_nextp;
+        f(*edgep);
+    }
+}
+
+void DfgVertex::forEachSinkEdge(std::function f) const {
+    for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) {
+        nextp = edgep->m_nextp;
+        f(*edgep);
+    }
+}
+
+const DfgEdge* DfgVertex::findSourceEdge(std::function p) const {
+    const auto pair = sourceEdges();
+    const DfgEdge* const edgesp = pair.first;
+    const size_t arity = pair.second;
+    for (size_t i = 0; i < arity; ++i) {
+        const DfgEdge& edge = edgesp[i];
+        if (p(edge, i)) return &edge;
+    }
+    return nullptr;
+}
+
+template 
+Vertex* DfgVertex::findSink(std::function p) const {
+    static_assert(std::is_base_of::value,
+                  "'Vertex' must be subclass of 'DfgVertex'");
+    for (DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) {
+        if (Vertex* const sinkp = edgep->m_sinkp->cast()) {
+            if (p(*sinkp)) return sinkp;
+        }
+    }
+    return nullptr;
+}
+
+template 
+Vertex* DfgVertex::findSink() const {
+    static_assert(!std::is_same::value,
+                  "'Vertex' must be proper subclass of 'DfgVertex'");
+    return findSink([](const Vertex&) { return true; });
+}
+
+//------------------------------------------------------------------------------
+// DfgVertex sub-types follow
+//------------------------------------------------------------------------------
+
+// Include macros generated by 'astgen'. These include DFGGEN_MEMBERS_
+// for each DfgVertex sub-type. The generated members include boilerplate
+// methods related to cloning, visitor dispatch, and other functionality.
+// For precise details please read the generated macros.
+#include "V3Dfg__gen_macros.h"
+
+//------------------------------------------------------------------------------
+// Implementation of dataflow graph vertices with a fixed number of sources
+//------------------------------------------------------------------------------
+
+template 
+class DfgVertexWithArity VL_NOT_FINAL : public DfgVertex {
+    static_assert(1 <= Arity && Arity <= 4, "Arity must be between 1 and 4 inclusive");
+
+    std::array m_srcs;  // Source edges
+
+protected:
+    DfgVertexWithArity(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
+        : DfgVertex{dfg, type, flp, dtypep} {
+        // Initialize source edges
+        for (size_t i = 0; i < Arity; ++i) m_srcs[i].init(this);
+    }
+
+    ~DfgVertexWithArity() override = default;
+
+public:
+    std::pair sourceEdges() final override {  //
+        return {m_srcs.data(), Arity};
+    }
+    std::pair sourceEdges() const final override {
+        return {m_srcs.data(), Arity};
+    }
+
+    template 
+    DfgEdge* sourceEdge() {
+        static_assert(Index < Arity, "Source index out of range");
+        return &m_srcs[Index];
+    }
+
+    template 
+    const DfgEdge* sourceEdge() const {
+        static_assert(Index < Arity, "Source index out of range");
+        return &m_srcs[Index];
+    }
+
+    template 
+    DfgVertex* source() const {
+        static_assert(Index < Arity, "Source index out of range");
+        return m_srcs[Index].sourcep();
+    }
+
+    template 
+    void relinkSource(DfgVertex* newSourcep) {
+        static_assert(Index < Arity, "Source index out of range");
+        UASSERT_OBJ(m_srcs[Index].sinkp() == this, this, "Inconsistent");
+        m_srcs[Index].relinkSource(newSourcep);
+    }
+};
+
+class DfgVertexUnary VL_NOT_FINAL : public DfgVertexWithArity<1> {
+protected:
+    DfgVertexUnary(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
+        : DfgVertexWithArity<1>{dfg, type, flp, dtypep} {}
+
+public:
+    ASTGEN_MEMBERS_DfgVertexUnary;
+
+    // Named getter/setter for sources
+    DfgVertex* srcp() const { return source<0>(); }
+    void srcp(DfgVertex* vtxp) { relinkSource<0>(vtxp); }
+};
+
+class DfgVertexBinary VL_NOT_FINAL : public DfgVertexWithArity<2> {
+protected:
+    DfgVertexBinary(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
+        : DfgVertexWithArity<2>{dfg, type, flp, dtypep} {}
+
+public:
+    ASTGEN_MEMBERS_DfgVertexBinary;
+
+    // Named getter/setter for sources
+    DfgVertex* lhsp() const { return source<0>(); }
+    void lhsp(DfgVertex* vtxp) { relinkSource<0>(vtxp); }
+    DfgVertex* rhsp() const { return source<1>(); }
+    void rhsp(DfgVertex* vtxp) { relinkSource<1>(vtxp); }
+};
+
+class DfgVertexTernary VL_NOT_FINAL : public DfgVertexWithArity<3> {
+protected:
+    DfgVertexTernary(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
+        : DfgVertexWithArity<3>{dfg, type, flp, dtypep} {}
+
+public:
+    ASTGEN_MEMBERS_DfgVertexTernary;
+};
+
+//------------------------------------------------------------------------------
+// Implementation of dataflow graph vertices with a variable number of sources
+//------------------------------------------------------------------------------
+
+class DfgVertexVariadic VL_NOT_FINAL : public DfgVertex {
+    DfgEdge* m_srcsp;  // The source edges
+    uint32_t m_srcCnt = 0;  // Number of sources used
+    uint32_t m_srcCap;  // Number of sources allocated
+
+    // Allocate a new source edge array
+    DfgEdge* allocSources(size_t n) {
+        DfgEdge* const srcsp = new DfgEdge[n];
+        for (size_t i = 0; i < n; ++i) srcsp[i].init(this);
+        return srcsp;
+    }
+
+    // Double the capacity of m_srcsp
+    void growSources() {
+        m_srcCap *= 2;
+        DfgEdge* const newsp = allocSources(m_srcCap);
+        for (size_t i = 0; i < m_srcCnt; ++i) {
+            DfgEdge* const oldp = m_srcsp + i;
+            // Skip over unlinked source edge
+            if (!oldp->sourcep()) continue;
+            // New edge driven from the same vertex as the old edge
+            newsp[i].relinkSource(oldp->sourcep());
+            // Unlink the old edge, it will be deleted
+            oldp->unlinkSource();
+        }
+        // Delete old source edges
+        delete[] m_srcsp;
+        // Keep hold of new source edges
+        m_srcsp = newsp;
+    }
+
+protected:
+    DfgVertexVariadic(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep,
+                      uint32_t initialCapacity = 1)
+        : DfgVertex{dfg, type, flp, dtypep}
+        , m_srcsp{allocSources(initialCapacity)}
+        , m_srcCap{initialCapacity} {}
+
+    ~DfgVertexVariadic() override { delete[] m_srcsp; };
+
+    DfgEdge* addSource() {
+        if (m_srcCnt == m_srcCap) growSources();
+        return m_srcsp + m_srcCnt++;
+    }
+
+    void resetSources() {
+        // #ifdef VL_DEBUG TODO: DEBUG ONLY
+        for (uint32_t i = 0; i < m_srcCnt; ++i) {
+            UASSERT_OBJ(!m_srcsp[i].sourcep(), m_srcsp[i].sourcep(), "Connected source");
+        }
+        // #endif
+        m_srcCnt = 0;
+    }
+
+public:
+    ASTGEN_MEMBERS_DfgVertexVariadic;
+
+    DfgEdge* sourceEdge(size_t idx) const { return &m_srcsp[idx]; }
+    DfgVertex* source(size_t idx) const { return m_srcsp[idx].sourcep(); }
+
+    std::pair sourceEdges() override { return {m_srcsp, m_srcCnt}; }
+    std::pair sourceEdges() const override { return {m_srcsp, m_srcCnt}; }
+};
+
+// DfgVertex subclasses
+#include "V3DfgVertices.h"
+
+// The rest of the DfgVertex subclasses are generated by 'astgen' from AstNodeMath nodes
+#include "V3Dfg__gen_auto_classes.h"
+
+DfgVertexVar* DfgGraph::varVerticesBeginp() const {
+    return static_cast(m_varVertices.begin());
+}
+DfgVertexVar* DfgGraph::varVerticesRbeginp() const {
+    return static_cast(m_varVertices.rbegin());
+}
+DfgConst* DfgGraph::constVerticesBeginp() const {
+    return static_cast(m_constVertices.begin());
+}
+DfgConst* DfgGraph::constVerticesRbeginp() const {
+    return static_cast(m_constVertices.rbegin());
+}
+DfgVertex* DfgGraph::opVerticesBeginp() const { return m_opVertices.begin(); }
+DfgVertex* DfgGraph::opVerticesRbeginp() const { return m_opVertices.rbegin(); }
+
+bool DfgVertex::isZero() const {
+    if (const DfgConst* const constp = cast()) return constp->isZero();
+    return false;
+}
+
+bool DfgVertex::isOnes() const {
+    if (const DfgConst* const constp = cast()) return constp->isOnes();
+    return false;
+}
+
+#endif
diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp
new file mode 100644
index 000000000..b494c81dc
--- /dev/null
+++ b/src/V3DfgAstToDfg.cpp
@@ -0,0 +1,520 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Convert AstModule to DfgGraph
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// Convert and AstModule to a DfgGraph. We proceed by visiting convertable logic blocks (e.g.:
+// AstAssignW of appropriate type and with no delays), recursively constructing DfgVertex instances
+// for the expressions that compose the subject logic block. If all expressions in the current
+// logic block can be converted, then we delete the logic block (now represented in the DfgGraph),
+// and connect the corresponding DfgVertex instances appropriately. If some of the expressions were
+// not convertible in the current logic block, we revert (delete) the DfgVertex instances created
+// for the logic block, and leave the logic block in the AstModule. Any variable reference from
+// non-converted logic blocks (or other constructs under the AstModule) are marked as being
+// referenced in the AstModule, which is relevant for later optimization.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+#include "V3Dfg.h"
+#include "V3DfgPasses.h"
+#include "V3Error.h"
+#include "V3Global.h"
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+namespace {
+
+// Create a DfgVertex out of a AstNodeMath. For most AstNodeMath subtypes, this can be done
+// automatically. For the few special cases, we provide specializations below
+template 
+Vertex* makeVertex(const Node* nodep, DfgGraph& dfg) {
+    return new Vertex{dfg, nodep->fileline(), DfgVertex::dtypeFor(nodep)};
+}
+
+//======================================================================
+// Currently unhandled nodes
+// LCOV_EXCL_START
+// AstCCast changes width, but should not exists where DFG optimization is currently invoked
+template <>
+DfgCCast* makeVertex(const AstCCast*, DfgGraph&) {
+    return nullptr;
+}
+// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway
+template <>
+DfgAtoN* makeVertex(const AstAtoN*, DfgGraph&) {
+    return nullptr;
+}
+// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway
+template <>
+DfgCompareNN* makeVertex(const AstCompareNN*, DfgGraph&) {
+    return nullptr;
+}
+// Unhandled in DfgToAst, but also operates on unpacked arrays which we don't optimize anyway
+template <>
+DfgSliceSel* makeVertex(const AstSliceSel*, DfgGraph&) {
+    return nullptr;
+}
+// LCOV_EXCL_STOP
+
+}  // namespace
+
+class AstToDfgVisitor final : public VNVisitor {
+    // NODE STATE
+
+    // AstNode::user1p   // DfgVertex for this AstNode
+    const VNUser1InUse m_user1InUse;
+
+    // STATE
+
+    DfgGraph* const m_dfgp;  // The graph being built
+    V3DfgOptimizationContext& m_ctx;  // The optimization context for stats
+    bool m_foundUnhandled = false;  // Found node not implemented as DFG or not implemented 'visit'
+    std::vector m_uncommittedVertices;  // Vertices that we might decide to revert
+    bool m_converting = false;  // We are trying to convert some logic at the moment
+    std::vector m_varPackedps;  // All the DfgVarPacked vertices we created.
+    std::vector m_varArrayps;  // All the DfgVarArray vertices we created.
+
+    // METHODS
+    void markReferenced(AstNode* nodep) {
+        nodep->foreach([this](const AstVarRef* refp) {
+            // No need to (and in fact cannot) mark variables with unsupported dtypes
+            if (!DfgVertex::isSupportedDType(refp->varp()->dtypep())) return;
+            getNet(refp->varp())->setHasModRefs();
+        });
+    }
+
+    void commitVertices() { m_uncommittedVertices.clear(); }
+
+    void revertUncommittedVertices() {
+        for (DfgVertex* const vtxp : m_uncommittedVertices) vtxp->unlinkDelete(*m_dfgp);
+        m_uncommittedVertices.clear();
+    }
+
+    DfgVertexVar* getNet(AstVar* varp) {
+        if (!varp->user1p()) {
+            // Note DfgVertexVar vertices are not added to m_uncommittedVertices, because we
+            // want to hold onto them via AstVar::user1p, and the AstVar might be referenced via
+            // multiple AstVarRef instances, so we will never revert a DfgVertexVar once
+            // created. We will delete unconnected variable vertices at the end.
+            if (VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
+                DfgVarArray* const vtxp = new DfgVarArray{*m_dfgp, varp};
+                varp->user1p();
+                m_varArrayps.push_back(vtxp);
+                varp->user1p(vtxp);
+            } else {
+                DfgVarPacked* const vtxp = new DfgVarPacked{*m_dfgp, varp};
+                m_varPackedps.push_back(vtxp);
+                varp->user1p(vtxp);
+            }
+        }
+        return varp->user1u().to();
+    }
+
+    DfgVertex* getVertex(AstNode* nodep) {
+        DfgVertex* vtxp = nodep->user1u().to();
+        UASSERT_OBJ(vtxp, nodep, "Missing Dfg vertex");
+        return vtxp;
+    }
+
+    // Returns true if the expression cannot (or should not) be represented by DFG
+    bool unhandled(AstNodeMath* nodep) {
+        // Short-circuiting if something was already unhandled
+        if (!m_foundUnhandled) {
+            // Impure nodes cannot be represented
+            if (!nodep->isPure()) {
+                m_foundUnhandled = true;
+                ++m_ctx.m_nonRepImpure;
+            }
+            // Check node has supported dtype
+            if (!DfgVertex::isSupportedDType(nodep->dtypep())) {
+                m_foundUnhandled = true;
+                ++m_ctx.m_nonRepDType;
+            }
+        }
+        return m_foundUnhandled;
+    }
+
+    // Build DfgEdge representing the LValue assignment. Returns false if unsuccessful.
+    bool convertAssignment(FileLine* flp, AstNode* nodep, DfgVertex* vtxp) {
+        if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
+            m_foundUnhandled = false;
+            visit(vrefp);
+            if (m_foundUnhandled) return false;
+            getVertex(vrefp)->as()->addDriver(flp, 0, vtxp);
+            return true;
+        }
+        if (AstSel* const selp = VN_CAST(nodep, Sel)) {
+            AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
+            const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const);
+            if (!vrefp || !lsbp || !VN_IS(selp->widthp(), Const)) {
+                ++m_ctx.m_nonRepLhs;
+                return false;
+            }
+            m_foundUnhandled = false;
+            visit(vrefp);
+            if (m_foundUnhandled) return false;
+            getVertex(vrefp)->as()->addDriver(flp, lsbp->toUInt(), vtxp);
+            return true;
+        }
+        if (AstArraySel* const selp = VN_CAST(nodep, ArraySel)) {
+            AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
+            const AstConst* const idxp = VN_CAST(selp->bitp(), Const);
+            if (!vrefp || !idxp) {
+                ++m_ctx.m_nonRepLhs;
+                return false;
+            }
+            m_foundUnhandled = false;
+            visit(vrefp);
+            if (m_foundUnhandled) return false;
+            getVertex(vrefp)->as()->addDriver(flp, idxp->toUInt(), vtxp);
+            return true;
+        }
+        if (AstConcat* const concatp = VN_CAST(nodep, Concat)) {
+            AstNode* const lhsp = concatp->lhsp();
+            AstNode* const rhsp = concatp->rhsp();
+
+            {
+                FileLine* const lFlp = lhsp->fileline();
+                DfgSel* const lVtxp = new DfgSel{*m_dfgp, lFlp, DfgVertex::dtypeFor(lhsp)};
+                lVtxp->fromp(vtxp);
+                lVtxp->lsb(rhsp->width());
+                if (!convertAssignment(flp, lhsp, lVtxp)) return false;
+            }
+
+            {
+                FileLine* const rFlp = rhsp->fileline();
+                DfgSel* const rVtxp = new DfgSel{*m_dfgp, rFlp, DfgVertex::dtypeFor(rhsp)};
+                rVtxp->fromp(vtxp);
+                rVtxp->lsb(0);
+                return convertAssignment(flp, rhsp, rVtxp);
+            }
+        }
+        ++m_ctx.m_nonRepLhs;
+        return false;
+    }
+
+    bool convertEquation(AstNode* nodep, FileLine* flp, AstNode* lhsp, AstNode* rhsp) {
+        UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest");
+
+        // Currently cannot handle direct assignments between unpacked types. These arise e.g.
+        // when passing an unpacked array through a module port.
+        if (!DfgVertex::isSupportedPackedDType(lhsp->dtypep())
+            || !DfgVertex::isSupportedPackedDType(rhsp->dtypep())) {
+            markReferenced(nodep);
+            ++m_ctx.m_nonRepDType;
+            return false;
+        }
+
+        // Cannot handle mismatched widths. Mismatched assignments should have been fixed up in
+        // earlier passes anyway, so this should never be hit, but being paranoid just in case.
+        if (lhsp->width() != rhsp->width()) {  // LCOV_EXCL_START
+            markReferenced(nodep);
+            ++m_ctx.m_nonRepWidth;
+            return false;
+        }  // LCOV_EXCL_STOP
+
+        VL_RESTORER(m_converting);
+        m_converting = true;
+
+        m_foundUnhandled = false;
+        iterate(rhsp);
+        if (m_foundUnhandled) {
+            revertUncommittedVertices();
+            markReferenced(nodep);
+            return false;
+        }
+
+        if (!convertAssignment(flp, lhsp, getVertex(rhsp))) {
+            revertUncommittedVertices();
+            markReferenced(nodep);
+            return false;
+        }
+
+        // Connect the rhs vertex to the driven edge
+        commitVertices();
+
+        // Remove node from Ast. Now represented by the Dfg.
+        VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
+
+        //
+        ++m_ctx.m_representable;
+        return true;
+    }
+
+    // Canonicalize packed variables
+    void canonicalizePacked() {
+        for (DfgVarPacked* const varp : m_varPackedps) {
+            // Delete variables with no sinks nor sources (this can happen due to reverting
+            // uncommitted vertices, which does not remove variables)
+            if (!varp->hasSinks() && varp->arity() == 0) {
+                VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp);
+                continue;
+            }
+
+            // Gather (and unlink) all drivers
+            struct Driver {
+                FileLine* flp;
+                uint32_t lsb;
+                DfgVertex* vtxp;
+                Driver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp)
+                    : flp{flp}
+                    , lsb{lsb}
+                    , vtxp{vtxp} {}
+            };
+            std::vector drivers;
+            drivers.reserve(varp->arity());
+            varp->forEachSourceEdge([varp, &drivers](DfgEdge& edge, size_t idx) {
+                UASSERT(edge.sourcep(), "Should not have created undriven sources");
+                drivers.emplace_back(varp->driverFileLine(idx), varp->driverLsb(idx),
+                                     edge.sourcep());
+                edge.unlinkSource();
+            });
+
+            // Sort drivers by LSB
+            std::stable_sort(drivers.begin(), drivers.end(),
+                             [](const Driver& a, const Driver& b) { return a.lsb < b.lsb; });
+
+            // TODO: bail on multidriver
+
+            // Coalesce adjacent ranges
+            for (size_t i = 0, j = 1; j < drivers.size(); ++j) {
+                Driver& a = drivers[i];
+                Driver& b = drivers[j];
+
+                // Coalesce adjacent range
+                const uint32_t aWidth = a.vtxp->width();
+                const uint32_t bWidth = b.vtxp->width();
+                if (a.lsb + aWidth == b.lsb) {
+                    const auto dtypep = DfgVertex::dtypeForWidth(aWidth + bWidth);
+                    DfgConcat* const concatp = new DfgConcat{*m_dfgp, a.flp, dtypep};
+                    concatp->rhsp(a.vtxp);
+                    concatp->lhsp(b.vtxp);
+                    a.vtxp = concatp;
+                    b.vtxp = nullptr;  // Mark as moved
+                    ++m_ctx.m_coalescedAssignments;
+                    continue;
+                }
+
+                ++i;
+
+                // Compact non-adjacent ranges within the vector
+                if (j != i) {
+                    Driver& c = drivers[i];
+                    UASSERT_OBJ(!c.vtxp, c.flp, "Should have been marked moved");
+                    c = b;
+                    b.vtxp = nullptr;  // Mark as moved
+                }
+            }
+
+            // Reinsert sources in order
+            varp->resetSources();
+            for (const Driver& driver : drivers) {
+                if (!driver.vtxp) break;  // Stop at end of cmpacted list
+                varp->addDriver(driver.flp, driver.lsb, driver.vtxp);
+            }
+        }
+    }
+
+    // Canonicalize array variables
+    void canonicalizeArray() {
+        for (DfgVarArray* const varp : m_varArrayps) {
+            // Delete variables with no sinks nor sources (this can happen due to reverting
+            // uncommitted vertices, which does not remove variables)
+            if (!varp->hasSinks() && varp->arity() == 0) {
+                VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp);
+            }
+        }
+    }
+
+    // VISITORS
+    void visit(AstNode* nodep) override {
+        // Conservatively treat this node as unhandled
+        if (!m_foundUnhandled && m_converting) ++m_ctx.m_nonRepUnknown;
+        m_foundUnhandled = true;
+        markReferenced(nodep);
+    }
+    void visit(AstCell* nodep) override { markReferenced(nodep); }
+    void visit(AstNodeProcedure* nodep) override { markReferenced(nodep); }
+    void visit(AstVar* nodep) override {
+        // No need to (and in fact cannot) handle variables with unsupported dtypes
+        if (!DfgVertex::isSupportedDType(nodep->dtypep())) return;
+        // Mark ports as having external references
+        if (nodep->isIO()) getNet(nodep)->setHasExtRefs();
+        // Mark variables that are the target of a hierarchical reference
+        // (these flags were set up in DataflowPrepVisitor)
+        if (nodep->user2()) getNet(nodep)->setHasExtRefs();
+    }
+
+    void visit(AstAssignW* nodep) override {
+        ++m_ctx.m_inputEquations;
+
+        // Cannot handle assignment with timing control yet
+        if (nodep->timingControlp()) {
+            markReferenced(nodep);
+            ++m_ctx.m_nonRepTiming;
+            return;
+        }
+
+        convertEquation(nodep, nodep->fileline(), nodep->lhsp(), nodep->rhsp());
+    }
+
+    void visit(AstAlways* nodep) override {
+        // Ignore sequential logic, or if there are multiple statements
+        const VAlwaysKwd kwd = nodep->keyword();
+        if (nodep->sensesp() || !nodep->isJustOneBodyStmt()
+            || (kwd != VAlwaysKwd::ALWAYS && kwd != VAlwaysKwd::ALWAYS_COMB)) {
+            markReferenced(nodep);
+            return;
+        }
+
+        AstNode* const stmtp = nodep->stmtsp();
+
+        if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) {
+            ++m_ctx.m_inputEquations;
+            if (assignp->timingControlp()) {
+                markReferenced(stmtp);
+                ++m_ctx.m_nonRepTiming;
+                return;
+            }
+            convertEquation(nodep, assignp->fileline(), assignp->lhsp(), assignp->rhsp());
+        } else if (AstIf* const ifp = VN_CAST(stmtp, If)) {
+            // Will only handle single assignments to the same LHS in both branches
+            AstAssign* const thenp = VN_CAST(ifp->thensp(), Assign);
+            AstAssign* const elsep = VN_CAST(ifp->elsesp(), Assign);
+            if (!thenp || !elsep || thenp->nextp() || elsep->nextp()
+                || !thenp->lhsp()->sameTree(elsep->lhsp())) {
+                markReferenced(stmtp);
+                return;
+            }
+
+            ++m_ctx.m_inputEquations;
+            if (thenp->timingControlp() || elsep->timingControlp()) {
+                markReferenced(stmtp);
+                ++m_ctx.m_nonRepTiming;
+                return;
+            }
+
+            // Create a conditional for the rhs by borrowing the components from the AstIf
+            AstCond* const rhsp = new AstCond{ifp->fileline(),  //
+                                              ifp->condp()->unlinkFrBack(),  //
+                                              thenp->rhsp()->unlinkFrBack(),  //
+                                              elsep->rhsp()->unlinkFrBack()};
+
+            if (!convertEquation(nodep, ifp->fileline(), thenp->lhsp(), rhsp)) {
+                // Failed to convert. Mark 'rhsp', as 'convertEquation' only marks 'nodep'.
+                markReferenced(rhsp);
+                // Put the AstIf back together
+                ifp->condp(rhsp->condp()->unlinkFrBack());
+                thenp->rhsp(rhsp->thenp()->unlinkFrBack());
+                elsep->rhsp(rhsp->elsep()->unlinkFrBack());
+            }
+            // Delete the auxiliary conditional
+            VL_DO_DANGLING(rhsp->deleteTree(), rhsp);
+        } else {
+            markReferenced(stmtp);
+        }
+    }
+
+    void visit(AstVarRef* nodep) override {
+        UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");
+        if (unhandled(nodep)) return;
+
+        if (nodep->access().isRW()  // Cannot represent read-write references
+            || nodep->varp()->isIfaceRef()  // Cannot handle interface references
+            || nodep->varp()->delayp()  // Cannot handle delayed variables
+            || nodep->classOrPackagep()  // Cannot represent cross module references
+        ) {
+            markReferenced(nodep);
+            m_foundUnhandled = true;
+            ++m_ctx.m_nonRepVarRef;
+            return;
+        }
+
+        // Sadly sometimes AstVarRef does not have the same dtype as the referenced variable
+        if (!DfgVertex::isSupportedDType(nodep->varp()->dtypep())) {
+            m_foundUnhandled = true;
+            ++m_ctx.m_nonRepVarRef;
+            return;
+        }
+
+        nodep->user1p(getNet(nodep->varp()));
+    }
+
+    void visit(AstConst* nodep) override {
+        UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");
+        if (unhandled(nodep)) return;
+        DfgVertex* const vtxp = new DfgConst{*m_dfgp, nodep->fileline(), nodep->num()};
+        m_uncommittedVertices.push_back(vtxp);
+        nodep->user1p(vtxp);
+    }
+
+    void visit(AstSel* nodep) override {
+        UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");
+        if (unhandled(nodep)) return;
+        if (!VN_IS(nodep->widthp(), Const)) {  // This should never be taken, but paranoia
+            m_foundUnhandled = true;
+            ++m_ctx.m_nonRepNode;
+            return;
+        }
+        iterate(nodep->fromp());
+        if (m_foundUnhandled) return;
+
+        FileLine* const flp = nodep->fileline();
+        DfgVertex* vtxp = nullptr;
+        if (AstConst* const constp = VN_CAST(nodep->lsbp(), Const)) {
+            DfgSel* const selp = new DfgSel{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)};
+            selp->fromp(nodep->fromp()->user1u().to());
+            selp->lsb(constp->toUInt());
+            vtxp = selp;
+        } else {
+            iterate(nodep->lsbp());
+            if (m_foundUnhandled) return;
+            DfgMux* const muxp = new DfgMux{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)};
+            muxp->fromp(nodep->fromp()->user1u().to());
+            muxp->lsbp(nodep->lsbp()->user1u().to());
+            vtxp = muxp;
+        }
+        m_uncommittedVertices.push_back(vtxp);
+        nodep->user1p(vtxp);
+    }
+
+// The rest of the 'visit' methods are generated by 'astgen'
+#include "V3Dfg__gen_ast_to_dfg.h"
+
+    // CONSTRUCTOR
+    explicit AstToDfgVisitor(AstModule& module, V3DfgOptimizationContext& ctx)
+        : m_dfgp{new DfgGraph{module, module.name()}}
+        , m_ctx{ctx} {
+        // Build the DFG
+        iterateChildren(&module);
+        UASSERT_OBJ(m_uncommittedVertices.empty(), &module, "Uncommitted vertices remain");
+
+        // Canonicalize variables
+        canonicalizePacked();
+        canonicalizeArray();
+    }
+
+public:
+    static DfgGraph* apply(AstModule& module, V3DfgOptimizationContext& ctx) {
+        return AstToDfgVisitor{module, ctx}.m_dfgp;
+    }
+};
+
+DfgGraph* V3DfgPasses::astToDfg(AstModule& module, V3DfgOptimizationContext& ctx) {
+    return AstToDfgVisitor::apply(module, ctx);
+}
diff --git a/src/V3DfgDecomposition.cpp b/src/V3DfgDecomposition.cpp
new file mode 100644
index 000000000..d12e9946e
--- /dev/null
+++ b/src/V3DfgDecomposition.cpp
@@ -0,0 +1,544 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: DfgGraph decomposition algorithms
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+//  Algorithms that take a DfgGraph and decompose it into multiple DfgGraphs.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Dfg.h"
+#include "V3File.h"
+
+#include 
+#include 
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+class SplitIntoComponents final {
+
+    // STATE
+    DfgGraph& m_dfg;  // The input graph
+    const std::string m_prefix;  // Component name prefix
+    std::vector> m_components;  // The extracted components
+    // Component counter - starting from 1 as 0 is the default value used as a marker
+    size_t m_componentCounter = 1;
+
+    void colorComponents() {
+        // Work queue for depth first traversal starting from this vertex
+        std::vector queue;
+        queue.reserve(m_dfg.size());
+
+        // any sort of interesting logic must involve a variable, so we only need to iterate them
+        for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            // If already assigned this vertex to a component, then continue
+            if (vtxp->user()) continue;
+
+            // Start depth first traversal at this vertex
+            queue.push_back(vtxp);
+
+            // Depth first traversal
+            do {
+                // Pop next work item
+                DfgVertex& item = *queue.back();
+                queue.pop_back();
+
+                // Move on if already visited
+                if (item.user()) continue;
+
+                // Assign to current component
+                item.user() = m_componentCounter;
+
+                // Enqueue all sources and sinks of this vertex.
+                item.forEachSource([&](DfgVertex& src) { queue.push_back(&src); });
+                item.forEachSink([&](DfgVertex& dst) { queue.push_back(&dst); });
+            } while (!queue.empty());
+
+            // Done with this component
+            ++m_componentCounter;
+        }
+    }
+
+    void moveVertices(DfgVertex* headp) {
+        for (DfgVertex *vtxp = headp, *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            DfgVertex& vtx = *vtxp;
+            if (const size_t component = vtx.user()) {
+                m_dfg.removeVertex(vtx);
+                m_components[component - 1]->addVertex(vtx);
+            } else {
+                // This vertex is not connected to a variable and is hence unused, remove here
+                vtx.unlinkDelete(m_dfg);
+            }
+        }
+    }
+
+    SplitIntoComponents(DfgGraph& dfg, std::string label)
+        : m_dfg{dfg}
+        , m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} {
+        // Component number is stored as DfgVertex::user()
+        const auto userDataInUse = m_dfg.userDataInUse();
+        // Color each component of the graph
+        colorComponents();
+        // Allocate the component graphs
+        m_components.resize(m_componentCounter - 1);
+        for (size_t i = 1; i < m_componentCounter; ++i) {
+            m_components[i - 1].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i - 1)});
+        }
+        // Move the vertices to the component graphs
+        moveVertices(m_dfg.varVerticesBeginp());
+        moveVertices(m_dfg.constVerticesBeginp());
+        moveVertices(m_dfg.opVerticesBeginp());
+        //
+        UASSERT(m_dfg.size() == 0, "'this' DfgGraph should have been emptied");
+    }
+
+public:
+    static std::vector> apply(DfgGraph& dfg, const std::string& label) {
+        return std::move(SplitIntoComponents{dfg, label}.m_components);
+    }
+};
+
+std::vector> DfgGraph::splitIntoComponents(std::string label) {
+    return SplitIntoComponents::apply(*this, label);
+}
+
+class ExtractCyclicComponents final {
+    static constexpr size_t UNASSIGNED = std::numeric_limits::max();
+
+    // TYPES
+    struct VertexState {
+        size_t index = UNASSIGNED;  // Used by Pearce's algorithm for detecting SCCs
+        size_t component = UNASSIGNED;  // Result component number (0 stays in input graph)
+        bool merged = false;  // Visited in the merging pass
+        VertexState(){};
+    };
+
+    // STATE
+
+    //==========================================================================
+    // Shared state
+
+    DfgGraph& m_dfg;  // The input graph
+    std::deque m_stateStorage;  // Container for VertexState instances
+    const std::string m_prefix;  // Component name prefix
+    size_t m_nonTrivialSCCs = 0;  // Number of non-trivial SCCs in the graph
+    const bool m_doExpensiveChecks = v3Global.opt.debugCheck();
+
+    //==========================================================================
+    // State for Pearce's algorithm for detecting SCCs
+
+    size_t m_index = 0;  // Visitation index counter
+    std::vector m_stack;  // The stack used by the algorithm
+
+    //==========================================================================
+    // State for extraction
+
+    // The extracted cyclic components
+    std::vector> m_components;
+    // Map from 'variable vertex' -> 'component index' -> 'clone in that component'
+    std::unordered_map> m_clones;
+
+    // METHODS
+
+    //==========================================================================
+    // Shared methods
+
+    VertexState& state(DfgVertex& vtx) const { return *vtx.getUser(); }
+
+    VertexState& allocState(DfgVertex& vtx) {
+        VertexState*& statep = vtx.user();
+        UASSERT_OBJ(!statep, &vtx, "Vertex state already allocated " << cvtToHex(statep));
+        m_stateStorage.emplace_back();
+        statep = &m_stateStorage.back();
+        return *statep;
+    }
+
+    VertexState& getOrAllocState(DfgVertex& vtx) {
+        VertexState*& statep = vtx.user();
+        if (!statep) {
+            m_stateStorage.emplace_back();
+            statep = &m_stateStorage.back();
+        }
+        return *statep;
+    }
+
+    //==========================================================================
+    // Methods for Pearce's algorithm to detect strongly connected components
+
+    void visitColorSCCs(DfgVertex& vtx, VertexState& vtxState) {
+        UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED, &vtx, "Already visited vertex"););
+
+        // Visiting vertex
+        const size_t rootIndex = vtxState.index = ++m_index;
+
+        // Visit children
+        vtx.forEachSink([&](DfgVertex& child) {
+            VertexState& childSatate = getOrAllocState(child);
+            // If the child has not yet been visited, then continue traversal
+            if (childSatate.index == UNASSIGNED) visitColorSCCs(child, childSatate);
+            // If the child is not in an SCC
+            if (childSatate.component == UNASSIGNED) {
+                if (vtxState.index > childSatate.index) vtxState.index = childSatate.index;
+            }
+        });
+
+        if (vtxState.index == rootIndex) {
+            // This is the 'root' of an SCC
+
+            // A trivial SCC contains only a single vertex
+            const bool isTrivial = m_stack.empty() || state(*m_stack.back()).index < rootIndex;
+            // We also need a separate component for vertices that drive themselves (which can
+            // happen for input like 'assign a = a'), as we want to extract them (they are cyclic).
+            const bool drivesSelf = vtx.findSink([&vtx](const DfgVertex& sink) {  //
+                return &vtx == &sink;
+            });
+
+            if (!isTrivial || drivesSelf) {
+                // Allocate new component
+                ++m_nonTrivialSCCs;
+                vtxState.component = m_nonTrivialSCCs;
+                while (!m_stack.empty()) {
+                    VertexState& topState = state(*m_stack.back());
+                    // Only higher nodes belong to the same SCC
+                    if (topState.index < rootIndex) break;
+                    m_stack.pop_back();
+                    topState.component = m_nonTrivialSCCs;
+                }
+            } else {
+                // Trivial SCC (and does not drive itself), so acyclic. Keep it in original graph.
+                vtxState.component = 0;
+            }
+        } else {
+            // Not the root of an SCC
+            m_stack.push_back(&vtx);
+        }
+    }
+
+    void colorSCCs() {
+        // Implements Pearce's algorithm to color the strongly connected components. For reference
+        // see "An Improved Algorithm for Finding the Strongly Connected Components of a Directed
+        // Graph", David J.Pearce, 2005.
+
+        // We can leverage some properties of the input graph to gain a bit of speed. Firstly, we
+        // know constant nodes have no in edges, so they cannot be part of a non-trivial SCC. Mark
+        // them as such without starting a whole traversal.
+        for (DfgConst *vtxp = m_dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            VertexState& vtxState = allocState(*vtxp);
+            vtxState.index = 0;
+            vtxState.component = 0;
+        }
+
+        // Next, we know that all SCCs must include a variable (as the input graph was converted
+        // from an AST, we can only have a cycle by going through a variable), so we only start
+        // traversals through them, and only if we know they have both in and out edges.
+        for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            if (vtxp->arity() > 0 && vtxp->hasSinks()) {
+                VertexState& vtxState = getOrAllocState(*vtxp);
+                // If not yet visited, start a traversal
+                if (vtxState.index == UNASSIGNED) visitColorSCCs(*vtxp, vtxState);
+            } else {
+                VertexState& vtxState = getOrAllocState(*vtxp);
+                UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED || vtxState.component == 0,
+                                       vtxp, "Non circular variable must be in a trivial SCC"););
+                vtxState.index = 0;
+                vtxState.component = 0;
+            }
+        }
+
+        // Finally, everything we did not visit through the traversal of a variable cannot be in an
+        // SCC, (otherwise we would have found it from a variable).
+        for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            VertexState& vtxState = getOrAllocState(*vtxp);
+            if (vtxState.index == UNASSIGNED) {
+                vtxState.index = 0;
+                vtxState.component = 0;
+            }
+        }
+    }
+
+    //==========================================================================
+    // Methods for merging
+
+    void visitMergeSCCs(DfgVertex& vtx, size_t targetComponent) {
+        VertexState& vtxState = state(vtx);
+
+        // Move on if already visited
+        if (vtxState.merged) return;
+
+        // Visiting vertex
+        vtxState.merged = true;
+
+        // Assign vertex to the target component
+        vtxState.component = targetComponent;
+
+        // Visit all neighbours. We stop at variable boundaries,
+        // which is where we will split the graphs
+        vtx.forEachSource([=](DfgVertex& other) {
+            if (other.is()) return;
+            visitMergeSCCs(other, targetComponent);
+        });
+        vtx.forEachSink([=](DfgVertex& other) {
+            if (other.is()) return;
+            visitMergeSCCs(other, targetComponent);
+        });
+    }
+
+    void mergeSCCs() {
+        // Ensure that component boundaries are always at variables, by merging SCCs. Merging stops
+        // at variable boundaries, so we don't need to iterate variables. Constants are reachable
+        // from their sinks, or ar unused, so we don't need to iterate them either.
+        for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            DfgVertex& vtx = *vtxp;
+            // Start DFS from each vertex that is in a non-trivial SCC, and merge everything
+            // that is reachable from it into this component.
+            if (const size_t target = state(vtx).component) visitMergeSCCs(vtx, target);
+        }
+    }
+
+    //==========================================================================
+    // Methods for extraction
+
+    // Retrieve clone of vertex in the given component
+    DfgVertexVar& getClone(DfgVertexVar& vtx, size_t component) {
+        UASSERT_OBJ(state(vtx).component != component, &vtx, "Vertex is in that component");
+        DfgVertexVar*& clonep = m_clones[&vtx][component];
+        if (!clonep) {
+            if (DfgVarPacked* const pVtxp = vtx.cast()) {
+                clonep = new DfgVarPacked{m_dfg, pVtxp->varp()};
+            } else if (DfgVarArray* const aVtxp = vtx.cast()) {
+                clonep = new DfgVarArray{m_dfg, aVtxp->varp()};
+            }
+            UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type");
+            VertexState& cloneStatep = allocState(*clonep);
+            cloneStatep.component = component;
+            // We need to mark both the original and the clone as having additional references
+            vtx.setHasModRefs();
+            clonep->setHasModRefs();
+        }
+        return *clonep;
+    }
+
+    // Fix up non-variable sources of a DfgVertexVar that are in a different component,
+    // using the provided 'relink' callback
+    template 
+    void fixSources(T_Vertex& vtx, std::function relink) {
+        static_assert(std::is_base_of::value,
+                      "'Vertex' must be a 'DfgVertexVar'");
+        const size_t component = state(vtx).component;
+        vtx.forEachSourceEdge([&](DfgEdge& edge, size_t idx) {
+            DfgVertex& source = *edge.sourcep();
+            // DfgVertexVar sources are fixed up by `fixSinks` on those sources
+            if (source.is()) return;
+            const size_t sourceComponent = state(source).component;
+            // Same component is OK
+            if (sourceComponent == component) return;
+            // Unlink the source edge (source is reconnected by 'relink'
+            edge.unlinkSource();
+            // Apply the fixup
+            DfgVertexVar& clone = getClone(vtx, sourceComponent);
+            relink(*(clone.as()), source, idx);
+        });
+    }
+
+    // Fix up sinks of given variable vertex that are in a different component
+    void fixSinks(DfgVertexVar& vtx) {
+        const size_t component = state(vtx).component;
+        vtx.forEachSinkEdge([&](DfgEdge& edge) {
+            const size_t sinkComponent = state(*edge.sinkp()).component;
+            // Same component is OK
+            if (sinkComponent == component) return;
+            // Relink the sink to read the clone
+            edge.relinkSource(&getClone(vtx, sinkComponent));
+        });
+    }
+
+    // Fix edges that cross components
+    void fixEdges(DfgVertexVar& vtx) {
+        if (DfgVarPacked* const vvtxp = vtx.cast()) {
+            fixSources(
+                *vvtxp, [&](DfgVarPacked& clone, DfgVertex& driver, size_t driverIdx) {
+                    clone.addDriver(vvtxp->driverFileLine(driverIdx),  //
+                                    vvtxp->driverLsb(driverIdx), &driver);
+                });
+            fixSinks(*vvtxp);
+            return;
+        }
+
+        if (DfgVarArray* const vvtxp = vtx.cast()) {
+            fixSources(  //
+                *vvtxp, [&](DfgVarArray& clone, DfgVertex& driver, size_t driverIdx) {
+                    clone.addDriver(vvtxp->driverFileLine(driverIdx),  //
+                                    vvtxp->driverIndex(driverIdx), &driver);
+                });
+            fixSinks(*vvtxp);
+            return;
+        }
+    }
+
+    static void packSources(DfgGraph& dfg) {
+        // Remove undriven variable sources
+        for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            if (DfgVarPacked* const varp = vtxp->cast()) {
+                varp->packSources();
+                if (!varp->hasSinks() && varp->arity() == 0) {
+                    VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
+                }
+                continue;
+            }
+            if (DfgVarArray* const varp = vtxp->cast()) {
+                varp->packSources();
+                if (!varp->hasSinks() && varp->arity() == 0) {
+                    VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
+                }
+                continue;
+            }
+        }
+    }
+
+    void moveVertices(DfgVertex* headp) {
+        for (DfgVertex *vtxp = headp, *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            DfgVertex& vtx = *vtxp;
+            if (const size_t component = state(vtx).component) {
+                m_dfg.removeVertex(vtx);
+                m_components[component - 1]->addVertex(vtx);
+            }
+        }
+    }
+
+    void checkEdges(DfgGraph& dfg) const {
+        // Check that:
+        // - Edges only cross components at variable boundaries
+        // - Variable vertex sources are all connected.
+        dfg.forEachVertex([&](DfgVertex& vtx) {
+            const size_t component = state(vtx).component;
+            vtx.forEachSource([&](DfgVertex& src) {
+                if (src.is()) return;  // OK to cross at variables
+                UASSERT_OBJ(component == state(src).component, &vtx,
+                            "Edge crossing components without variable involvement");
+            });
+            vtx.forEachSink([&](DfgVertex& snk) {
+                if (snk.is()) return;  // OK to cross at variables
+                UASSERT_OBJ(component == state(snk).component, &vtx,
+                            "Edge crossing components without variable involvement");
+            });
+            if (const DfgVertexVar* const vtxp = vtx.cast()) {
+                vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) {
+                    UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex");
+                });
+            }
+        });
+    }
+
+    void checkGraph(DfgGraph& dfg) const {
+        // Build set of vertices
+        std::unordered_set vertices{dfg.size()};
+        dfg.forEachVertex([&](const DfgVertex& vtx) { vertices.insert(&vtx); });
+
+        // Check that each edge connects to a vertex that is within the same graph
+        dfg.forEachVertex([&](DfgVertex& vtx) {
+            vtx.forEachSource([&](DfgVertex& src) {
+                UASSERT_OBJ(vertices.count(&src), &vtx, "Source vertex not in graph");
+            });
+            vtx.forEachSink([&](DfgVertex& snk) {
+                UASSERT_OBJ(vertices.count(&snk), &snk, "Sink vertex not in graph");
+            });
+        });
+    }
+
+    void extractComponents() {
+        // Allocate result graphs
+        m_components.resize(m_nonTrivialSCCs);
+        for (size_t i = 0; i < m_nonTrivialSCCs; ++i) {
+            m_components[i].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i)});
+        }
+
+        // Fix up edges crossing components (we can only do this at variable boundaries, and the
+        // earlier merging of components ensured crossing in fact only happen at variable
+        // boundaries). Note that fixing up the edges can create clones of variables. Clones do
+        // not need fixing up, so we do not need to iterate them.
+        DfgVertex* const lastp = m_dfg.varVerticesRbeginp();
+        for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            // It is possible the last vertex (with a nullptr for 'nextp') gets cloned, and hence
+            // it's 'nextp' would become none nullptr as the clone is added. However, we don't need
+            // to iterate clones anyway, so it's ok to get the 'nextp' early in the loop.
+            nextp = vtxp->verticesNext();
+            DfgVertexVar& vtx = *vtxp;
+            // Fix up the edges crossing components
+            fixEdges(vtx);
+            // Don't iterate clones added during this loop
+            if (vtxp == lastp) break;
+        }
+
+        // Pack sources of variables to remove the now undriven inputs
+        // (cloning might have unlinked some of the inputs),
+        packSources(m_dfg);
+        for (const auto& dfgp : m_components) packSources(*dfgp);
+
+        // Check results for consistency
+        if (VL_UNLIKELY(m_doExpensiveChecks)) {
+            checkEdges(m_dfg);
+            for (const auto& dfgp : m_components) checkEdges(*dfgp);
+        }
+
+        // Move other vertices to their component graphs
+        // After this, vertex states are invalid as we moved the vertices
+        moveVertices(m_dfg.varVerticesBeginp());
+        moveVertices(m_dfg.constVerticesBeginp());
+        moveVertices(m_dfg.opVerticesBeginp());
+
+        // Check results for consistency
+        if (VL_UNLIKELY(m_doExpensiveChecks)) {
+            checkGraph(m_dfg);
+            for (const auto& dfgp : m_components) checkGraph(*dfgp);
+        }
+    }
+
+    // CONSTRUCTOR - entry point
+    explicit ExtractCyclicComponents(DfgGraph& dfg, std::string label)
+        : m_dfg{dfg}
+        , m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} {
+        // VertexState is stored as user data
+        const auto userDataInUse = dfg.userDataInUse();
+        // Find all the non-trivial SCCs (and trivial cycles) in the graph
+        colorSCCs();
+        // If the graph was acyclic (which should be the common case),
+        // there will be no non-trivial SCCs, so we are done.
+        if (!m_nonTrivialSCCs) return;
+        // Ensure that component boundaries are always at variables, by merging SCCs
+        mergeSCCs();
+        // Extract the components
+        extractComponents();
+    }
+
+public:
+    static std::vector> apply(DfgGraph& dfg, const std::string& label) {
+        return std::move(ExtractCyclicComponents{dfg, label}.m_components);
+    }
+};
+
+std::vector> DfgGraph::extractCyclicComponents(std::string label) {
+    return ExtractCyclicComponents::apply(*this, label);
+}
diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp
new file mode 100644
index 000000000..1ac43d319
--- /dev/null
+++ b/src/V3DfgDfgToAst.cpp
@@ -0,0 +1,453 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Convert DfgGraph to AstModule
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// Convert DfgGraph back to AstModule. We recursively construct AstNodeMath expressions for each
+// DfgVertex which represents a storage location (e.g.: DfgVarPacked), or has multiple sinks
+// without driving a storage location (and hence needs a temporary variable to duplication). The
+// recursion stops when we reach a DfgVertex representing a storage location (e.g.: DfgVarPacked),
+// or a vertex that that has multiple sinks (as these nodes will have a [potentially new temporary]
+// corresponding// storage location). Redundant variables (those whose source vertex drives
+// multiple variables) are eliminated when possible. Vertices driving multiple variables are
+// rendered once, driving an arbitrarily (but deterministically) chosen canonical variable, and the
+// corresponding redundant variables are assigned from the canonical variable.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Dfg.h"
+#include "V3DfgPasses.h"
+#include "V3UniqueNames.h"
+
+#include 
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+namespace {
+
+// Create an AstNodeMath out of a DfgVertex. For most AstNodeMath subtypes, this can be done
+// automatically. For the few special cases, we provide specializations below
+template 
+Node* makeNode(const Vertex* vtxp, Ops... ops) {
+    Node* const nodep = new Node{vtxp->fileline(), ops...};
+    UASSERT_OBJ(nodep->width() == static_cast(vtxp->width()), vtxp,
+                "Incorrect width in AstNode created from DfgVertex "
+                    << vtxp->typeName() << ": " << nodep->width() << " vs " << vtxp->width());
+    return nodep;
+}
+
+//======================================================================
+// Vertices needing special conversion
+
+template <>
+AstCountOnes* makeNode(  //
+    const DfgCountOnes* vtxp, AstNodeMath* op1) {
+    AstCountOnes* const nodep = new AstCountOnes{vtxp->fileline(), op1};
+    // Set dtype same as V3Width
+    const int selwidth = V3Number::log2b(nodep->lhsp()->width()) + 1;
+    nodep->dtypeSetLogicSized(selwidth, VSigning::UNSIGNED);
+    return nodep;
+}
+
+template <>
+AstExtend* makeNode(  //
+    const DfgExtend* vtxp, AstNodeMath* op1) {
+    return new AstExtend{vtxp->fileline(), op1, static_cast(vtxp->width())};
+}
+
+template <>
+AstExtendS* makeNode(  //
+    const DfgExtendS* vtxp, AstNodeMath* op1) {
+    return new AstExtendS{vtxp->fileline(), op1, static_cast(vtxp->width())};
+}
+
+template <>
+AstShiftL* makeNode(  //
+    const DfgShiftL* vtxp, AstNodeMath* op1, AstNodeMath* op2) {
+    return new AstShiftL{vtxp->fileline(), op1, op2, static_cast(vtxp->width())};
+}
+
+template <>
+AstShiftR* makeNode(  //
+    const DfgShiftR* vtxp, AstNodeMath* op1, AstNodeMath* op2) {
+    return new AstShiftR{vtxp->fileline(), op1, op2, static_cast(vtxp->width())};
+}
+
+template <>
+AstShiftRS* makeNode(  //
+    const DfgShiftRS* vtxp, AstNodeMath* op1, AstNodeMath* op2) {
+    return new AstShiftRS{vtxp->fileline(), op1, op2, static_cast(vtxp->width())};
+}
+
+//======================================================================
+// Currently unhandled nodes - see corresponding AstToDfg functions
+// LCOV_EXCL_START
+template <>
+AstCCast* makeNode(const DfgCCast* vtxp, AstNodeMath*) {
+    vtxp->v3fatalSrc("not implemented");
+}
+template <>
+AstAtoN* makeNode(const DfgAtoN* vtxp, AstNodeMath*) {
+    vtxp->v3fatalSrc("not implemented");
+}
+template <>
+AstCompareNN*
+makeNode(const DfgCompareNN* vtxp,
+                                                                 AstNodeMath*, AstNodeMath*) {
+    vtxp->v3fatalSrc("not implemented");
+}
+template <>
+AstSliceSel* makeNode(
+    const DfgSliceSel* vtxp, AstNodeMath*, AstNodeMath*, AstNodeMath*) {
+    vtxp->v3fatalSrc("not implemented");
+}
+// LCOV_EXCL_STOP
+
+}  // namespace
+
+class DfgToAstVisitor final : DfgVisitor {
+    // NODE STATE
+    // AstVar::user1()  bool: this is a temporary we are introducing
+
+    const VNUser1InUse m_inuser1;
+
+    // STATE
+
+    AstModule* const m_modp;  // The parent/result module
+    V3DfgOptimizationContext& m_ctx;  // The optimization context for stats
+    AstNodeMath* m_resultp = nullptr;  // The result node of the current traversal
+    // Map from DfgVertex to the AstVar holding the value of that DfgVertex after conversion
+    std::unordered_map m_resultVars;
+    // Map from an AstVar, to the canonical AstVar that can be substituted for that AstVar
+    std::unordered_map m_canonVars;
+    V3UniqueNames m_tmpNames{"__VdfgTmp"};  // For generating temporary names
+
+    // METHODS
+
+    // Given a DfgVarPacked, return the canonical AstVar that can be used for this DfgVarPacked.
+    // Also builds the m_canonVars map as a side effect.
+    AstVar* getCanonicalVar(const DfgVarPacked* vtxp) {
+        // If variable driven (at least partially) outside the DFG, then we have no choice
+        if (!vtxp->isDrivenFullyByDfg()) return vtxp->varp();
+
+        // Look up map
+        const auto it = m_canonVars.find(vtxp->varp());
+        if (it != m_canonVars.end()) return it->second;
+
+        // Not known yet, compute it (for all vars driven fully from the same driver)
+        std::vector varps;
+        vtxp->source(0)->forEachSink([&](const DfgVertex& vtx) {
+            if (const DfgVarPacked* const varVtxp = vtx.cast()) {
+                if (varVtxp->isDrivenFullyByDfg()) varps.push_back(varVtxp);
+            }
+        });
+        UASSERT_OBJ(!varps.empty(), vtxp, "The input vtxp is always available");
+        std::stable_sort(varps.begin(), varps.end(),
+                         [](const DfgVarPacked* ap, const DfgVarPacked* bp) {
+                             if (ap->hasExtRefs() != bp->hasExtRefs()) return ap->hasExtRefs();
+                             const FileLine& aFl = *(ap->fileline());
+                             const FileLine& bFl = *(bp->fileline());
+                             if (const int cmp = aFl.operatorCompare(bFl)) return cmp < 0;
+                             return ap->varp()->name() < bp->varp()->name();
+                         });
+        AstVar* const canonVarp = varps.front()->varp();
+
+        // Add results to map
+        for (const DfgVarPacked* const varp : varps) m_canonVars.emplace(varp->varp(), canonVarp);
+
+        // Return it
+        return canonVarp;
+    }
+
+    // Given a DfgVertex, return an AstVar that will hold the value of the given DfgVertex once we
+    // are done with converting this Dfg into Ast form.
+    AstVar* getResultVar(DfgVertex* vtxp) {
+        const auto pair = m_resultVars.emplace(vtxp, nullptr);
+        AstVar*& varp = pair.first->second;
+        if (pair.second) {
+            // If this vertex is a DfgVarPacked, then we know the variable. If this node is not a
+            // DfgVarPacked, then first we try to find a DfgVarPacked driven by this node, and use
+            // that, otherwise we create a temporary
+            if (const DfgVarPacked* const thisDfgVarPackedp = vtxp->cast()) {
+                // This is a DfgVarPacked
+                varp = getCanonicalVar(thisDfgVarPackedp);
+            } else if (const DfgVarArray* const thisDfgVarArrayp = vtxp->cast()) {
+                // This is a DfgVarArray
+                varp = thisDfgVarArrayp->varp();
+            } else if (const DfgVarPacked* const sinkDfgVarPackedp = vtxp->findSink(
+                           [](const DfgVarPacked& var) { return var.isDrivenFullyByDfg(); })) {
+                // We found a DfgVarPacked driven fully by this node
+                varp = getCanonicalVar(sinkDfgVarPackedp);
+            } else {
+                // No DfgVarPacked driven fully by this node. Create a temporary.
+                // TODO: should we reuse parts when the AstVar is used as an rvalue?
+                const string name = m_tmpNames.get(vtxp->hash().toString());
+                // Note: It is ok for these temporary variables to be always unsigned. They are
+                // read only by other expressions within the graph and all expressions interpret
+                // their operands based on the expression type, not the operand type.
+                AstNodeDType* const dtypep = v3Global.rootp()->findBitDType(
+                    vtxp->width(), vtxp->width(), VSigning::UNSIGNED);
+                varp = new AstVar{vtxp->fileline(), VVarType::MODULETEMP, name, dtypep};
+                varp->user1(true);  // Mark as temporary
+                // Add temporary AstVar to containing module
+                m_modp->addStmtsp(varp);
+            }
+            // Add to map
+        }
+        return varp;
+    }
+
+    AstNodeMath* convertDfgVertexToAstNodeMath(DfgVertex* vtxp) {
+        UASSERT_OBJ(!m_resultp, vtxp, "Result already computed");
+        iterate(vtxp);
+        UASSERT_OBJ(m_resultp, vtxp, "Missing result");
+        AstNodeMath* const resultp = m_resultp;
+        m_resultp = nullptr;
+        return resultp;
+    }
+
+    bool inlineVertex(DfgVertex& vtx) {
+        // Inline vertices that drive only a single node, or are special
+        if (!vtx.hasMultipleSinks()) return true;
+        if (vtx.is()) return true;
+        if (vtx.is()) return true;
+        if (const DfgArraySel* const selp = vtx.cast()) {
+            return selp->bitp()->is();
+        }
+        return false;
+    }
+
+    AstNodeMath* convertSource(DfgVertex* vtxp) {
+        if (inlineVertex(*vtxp)) {
+            // Inlined vertices are simply recursively converted
+            UASSERT_OBJ(vtxp->hasSinks(), vtxp, "Must have one sink: " << vtxp->typeName());
+            return convertDfgVertexToAstNodeMath(vtxp);
+        } else {
+            // Vertices that are not inlined need a variable, just return a reference
+            return new AstVarRef{vtxp->fileline(), getResultVar(vtxp), VAccess::READ};
+        }
+    }
+
+    void convertCanonicalVarDriver(const DfgVarPacked* dfgVarp) {
+        const auto wRef = [dfgVarp]() {
+            return new AstVarRef{dfgVarp->fileline(), dfgVarp->varp(), VAccess::WRITE};
+        };
+        if (dfgVarp->isDrivenFullyByDfg()) {
+            // Whole variable is driven. Render driver and assign directly to whole variable.
+            AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(dfgVarp->source(0));
+            addResultEquation(dfgVarp->driverFileLine(0), wRef(), rhsp);
+        } else {
+            // Variable is driven partially. Render each driver as a separate assignment.
+            dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
+                UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources");
+                // Render the rhs expression
+                AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(edge.sourcep());
+                // Create select LValue
+                FileLine* const flp = dfgVarp->driverFileLine(idx);
+                AstConst* const lsbp = new AstConst{flp, dfgVarp->driverLsb(idx)};
+                AstConst* const widthp = new AstConst{flp, edge.sourcep()->width()};
+                AstSel* const lhsp = new AstSel{flp, wRef(), lsbp, widthp};
+                // Add assignment of the value to the selected bits
+                addResultEquation(flp, lhsp, rhsp);
+            });
+        }
+    }
+
+    void convertDuplicateVarDriver(const DfgVarPacked* dfgVarp, AstVar* canonVarp) {
+        const auto rRef = [canonVarp]() {
+            return new AstVarRef{canonVarp->fileline(), canonVarp, VAccess::READ};
+        };
+        const auto wRef = [dfgVarp]() {
+            return new AstVarRef{dfgVarp->fileline(), dfgVarp->varp(), VAccess::WRITE};
+        };
+        if (dfgVarp->isDrivenFullyByDfg()) {
+            // Whole variable is driven. Just assign from the canonical variable.
+            addResultEquation(dfgVarp->driverFileLine(0), wRef(), rRef());
+        } else {
+            // Variable is driven partially. Asign from parts of the canonical var.
+            dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
+                UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources");
+                // Create select LValue
+                FileLine* const flp = dfgVarp->driverFileLine(idx);
+                AstConst* const lsbp = new AstConst{flp, dfgVarp->driverLsb(idx)};
+                AstConst* const widthp = new AstConst{flp, edge.sourcep()->width()};
+                AstSel* const rhsp = new AstSel{flp, rRef(), lsbp, widthp->cloneTree(false)};
+                AstSel* const lhsp = new AstSel{flp, wRef(), lsbp->cloneTree(false), widthp};
+                // Add assignment of the value to the selected bits
+                addResultEquation(flp, lhsp, rhsp);
+            });
+        }
+    }
+
+    void convertArrayDiver(const DfgVarArray* dfgVarp) {
+        // Variable is driven partially. Asign from parts of the canonical var.
+        dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
+            UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources");
+            // Render the rhs expression
+            AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(edge.sourcep());
+            // Create select LValue
+            FileLine* const flp = dfgVarp->driverFileLine(idx);
+            AstVarRef* const refp = new AstVarRef{flp, dfgVarp->varp(), VAccess::WRITE};
+            AstConst* const idxp = new AstConst{flp, dfgVarp->driverIndex(idx)};
+            AstArraySel* const lhsp = new AstArraySel{flp, refp, idxp};
+            // Add assignment of the value to the selected bits
+            addResultEquation(flp, lhsp, rhsp);
+        });
+    }
+
+    void addResultEquation(FileLine* flp, AstNode* lhsp, AstNode* rhsp) {
+        m_modp->addStmtsp(new AstAssignW{flp, lhsp, rhsp});
+        ++m_ctx.m_resultEquations;
+    }
+
+    // VISITORS
+    void visit(DfgVertex* vtxp) override {  // LCOV_EXCL_START
+        vtxp->v3fatal("Unhandled DfgVertex: " << vtxp->typeName());
+    }  // LCOV_EXCL_STOP
+
+    void visit(DfgVarPacked* vtxp) override {
+        m_resultp = new AstVarRef{vtxp->fileline(), getCanonicalVar(vtxp), VAccess::READ};
+    }
+
+    void visit(DfgVarArray* vtxp) override {
+        m_resultp = new AstVarRef{vtxp->fileline(), vtxp->varp(), VAccess::READ};
+    }
+
+    void visit(DfgConst* vtxp) override {  //
+        m_resultp = new AstConst{vtxp->fileline(), vtxp->num()};
+    }
+
+    void visit(DfgSel* vtxp) override {
+        FileLine* const flp = vtxp->fileline();
+        AstNodeMath* const fromp = convertSource(vtxp->fromp());
+        AstConst* const lsbp = new AstConst{flp, vtxp->lsb()};
+        AstConst* const widthp = new AstConst{flp, vtxp->width()};
+        m_resultp = new AstSel{flp, fromp, lsbp, widthp};
+    }
+
+    void visit(DfgMux* vtxp) override {
+        FileLine* const flp = vtxp->fileline();
+        AstNodeMath* const fromp = convertSource(vtxp->fromp());
+        AstNodeMath* const lsbp = convertSource(vtxp->lsbp());
+        AstConst* const widthp = new AstConst{flp, vtxp->width()};
+        m_resultp = new AstSel{flp, fromp, lsbp, widthp};
+    }
+
+    // The rest of the 'visit' methods are generated by 'astgen'
+#include "V3Dfg__gen_dfg_to_ast.h"
+
+    // Constructor
+    explicit DfgToAstVisitor(DfgGraph& dfg, V3DfgOptimizationContext& ctx)
+        : m_modp{dfg.modulep()}
+        , m_ctx{ctx} {
+        // Convert the graph back to combinational assignments
+
+        // Used by DfgVertex::hash
+        const auto userDataInUse = dfg.userDataInUse();
+
+        // We can eliminate some variables completely
+        std::vector redundantVarps;
+
+        // First render variable assignments
+        for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+
+            // If there is no driver (this vertex is an input to the graph), then nothing to do.
+            if (!vtxp->isDrivenByDfg()) continue;
+
+            // Render packed variable assignments
+            if (const DfgVarPacked* const dfgVarp = vtxp->cast()) {
+                // The driver of this DfgVarPacked might drive multiple variables. Only emit one
+                // assignment from the driver to an arbitrarily chosen canonical variable, and
+                // assign the other variables from that canonical variable
+                AstVar* const canonVarp = getCanonicalVar(dfgVarp);
+                if (canonVarp == dfgVarp->varp()) {
+                    // This is the canonical variable, so render the driver
+                    convertCanonicalVarDriver(dfgVarp);
+                } else if (dfgVarp->keep()) {
+                    // Not the canonical variable but it must be kept
+                    convertDuplicateVarDriver(dfgVarp, canonVarp);
+                } else {
+                    // Not a canonical var, and it can be removed. We will replace all references
+                    // to it with the canonical variable, and hence this can be removed.
+                    redundantVarps.push_back(dfgVarp->varp());
+                    ++m_ctx.m_replacedVars;
+                }
+                // Done
+                continue;
+            }
+
+            // Render array variable assignments
+            if (const DfgVarArray* dfgVarp = vtxp->cast()) {
+                // We don't canonicalize arrays, so just render the drivers
+                convertArrayDiver(dfgVarp);
+                // Done
+                continue;
+            }
+        }
+
+        // Constants are always inlined, so we only need to iterate proper operations
+        for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+
+            // If the vertex is known to be inlined, then there is nothing to do
+            if (inlineVertex(*vtxp)) continue;
+
+            // Check if this uses a temporary, vs one of the vars rendered above
+            AstVar* const resultVarp = getResultVar(vtxp);
+            if (resultVarp->user1()) {
+                // We introduced a temporary for this DfgVertex
+                ++m_ctx.m_intermediateVars;
+                FileLine* const flp = vtxp->fileline();
+                // Just render the logic
+                AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(vtxp);
+                // The lhs is the temporary
+                AstNodeMath* const lhsp = new AstVarRef{flp, resultVarp, VAccess::WRITE};
+                // Add assignment of the value to the variable
+                addResultEquation(flp, lhsp, rhsp);
+            }
+        }
+
+        // Remap all references to point to the canonical variables, if one exists
+        VNDeleter deleter;
+        m_modp->foreach([&](AstVarRef* refp) {
+            // Any variable that is written outside the DFG will have itself as the canonical
+            // var, so need not be replaced, furthermore, if a variable is traced, we don't
+            // want to update the write ref we just created above, so we only replace read only
+            // references.
+            if (!refp->access().isReadOnly()) return;
+            const auto it = m_canonVars.find(refp->varp());
+            if (it == m_canonVars.end()) return;
+            if (it->second == refp->varp()) return;
+            refp->replaceWith(new AstVarRef{refp->fileline(), it->second, refp->access()});
+            deleter.pushDeletep(refp);
+        });
+
+        // Remove redundant variables
+        for (AstVar* const varp : redundantVarps) varp->unlinkFrBack()->deleteTree();
+    }
+
+public:
+    static AstModule* apply(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
+        return DfgToAstVisitor{dfg, ctx}.m_modp;
+    }
+};
+
+AstModule* V3DfgPasses::dfgToAst(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
+    return DfgToAstVisitor::apply(dfg, ctx);
+}
diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp
new file mode 100644
index 000000000..b162f6819
--- /dev/null
+++ b/src/V3DfgOptimizer.cpp
@@ -0,0 +1,305 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Dataflow based optimization of combinational logic
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// High level entry points from Ast world to the DFG optimizer.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3DfgOptimizer.h"
+
+#include "V3Ast.h"
+#include "V3AstUserAllocator.h"
+#include "V3Dfg.h"
+#include "V3DfgPasses.h"
+#include "V3Error.h"
+#include "V3Global.h"
+#include "V3Graph.h"
+#include "V3UniqueNames.h"
+
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+// Extract more combinational logic equations from procedures for better optimization opportunities
+class DataflowExtractVisitor final : public VNVisitor {
+    // NODE STATE
+    // AstVar::user3            -> bool: Flag indicating variable is subject of force or release
+    // statement AstVar::user4  -> bool: Flag indicating variable is combinationally driven
+    // AstNodeModule::user4     -> Extraction candidates (via m_extractionCandidates)
+    const VNUser3InUse m_user3InUse;
+    const VNUser4InUse m_user4InUse;
+
+    // Expressions considered for extraction as separate assignment to gain more opportunities for
+    // optimization, together with the list of variables they read.
+    using Candidates = std::vector>>;
+
+    // Expressions considered for extraction. All the candidates are pure expressions.
+    AstUser4Allocator m_extractionCandidates;
+
+    // STATE
+    AstNodeModule* m_modp = nullptr;  // The module being visited
+    Candidates* m_candidatesp = nullptr;
+    bool m_impure = false;  // True if the visited tree has a side effect
+    bool m_inForceReleaseLhs = false;  // Iterating LHS of force/release
+    // List of AstVar nodes read by the visited tree. 'vector' rather than 'set' as duplicates are
+    // somewhat unlikely and we can handle them later.
+    std::vector m_readVars;
+
+    // METHODS
+
+    // Node considered for extraction as a combinational equation. Trace variable usage/purity.
+    void iterateExtractionCandidate(AstNode* nodep) {
+        UASSERT_OBJ(!VN_IS(nodep->backp(), NodeMath), nodep,
+                    "Should not try to extract nested expressions (only root expressions)");
+
+        // Simple VarRefs should not be extracted, as they only yield trivial assignments.
+        // Similarly, don't extract anything if no candidate map is set up (for non-modules).
+        // We still need to visit them though, to mark hierarchical references.
+        if (VN_IS(nodep, NodeVarRef) || !m_candidatesp) {
+            iterate(nodep);
+            return;
+        }
+
+        // Don't extract plain constants
+        if (VN_IS(nodep, Const)) return;
+
+        // Candidates can't nest, so no need for VL_RESTORER, just initialize iteration state
+        m_impure = false;
+        m_readVars.clear();
+
+        // Trace variable usage
+        iterate(nodep);
+
+        // We only extract pure expressions
+        if (m_impure) return;
+
+        // Do not extract expressions without any variable references
+        if (m_readVars.empty()) return;
+
+        // Add to candidate list
+        m_candidatesp->emplace_back(VN_AS(nodep, NodeMath), std::move(m_readVars));
+    }
+
+    // VISIT methods
+
+    void visit(AstNetlist* nodep) override {
+        // Analyse the whole design
+        iterateChildrenConst(nodep);
+
+        // Replace candidate expressions only reading combinationally driven signals with variables
+        V3UniqueNames names{"__VdfgExtracted"};
+        for (AstNodeModule* modp = nodep->modulesp(); modp;
+             modp = VN_AS(modp->nextp(), NodeModule)) {
+            // Only extract from proper modules
+            if (!VN_IS(modp, Module)) continue;
+
+            for (const auto& pair : m_extractionCandidates(modp)) {
+                AstNodeMath* const nodep = pair.first;
+
+                // Do not extract expressions without any variable references
+                if (pair.second.empty()) continue;
+
+                // Check if all variables read by this expression are driven combinationally,
+                // and move on if not. Also don't extract it if one of the variables is subject
+                // to a force/release, as releasing nets must have immediate effect, but adding
+                // extra combinational logic can change semantics (see t_force_release_net*).
+                {
+                    bool hasBadVar = false;
+                    for (const AstVar* const readVarp : pair.second) {
+                        // variable is target of force/release or not combinationally driven
+                        if (readVarp->user3() || !readVarp->user4()) {
+                            hasBadVar = true;
+                            break;
+                        }
+                    }
+                    if (hasBadVar) continue;
+                }
+
+                // Create temporary variable
+                FileLine* const flp = nodep->fileline();
+                const string name = names.get(nodep);
+                AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, nodep->dtypep()};
+                varp->trace(false);
+                modp->addStmtsp(varp);
+
+                // Replace expression with temporary variable
+                nodep->replaceWith(new AstVarRef{flp, varp, VAccess::READ});
+
+                // Add assignment driving temporary variable
+                modp->addStmtsp(
+                    new AstAssignW{flp, new AstVarRef{flp, varp, VAccess::WRITE}, nodep});
+            }
+        }
+    }
+
+    void visit(AstNodeModule* nodep) override {
+        VL_RESTORER(m_modp);
+        m_modp = nodep;
+        iterateChildrenConst(nodep);
+    }
+
+    void visit(AstAlways* nodep) override {
+        VL_RESTORER(m_candidatesp);
+        // Only extract from combinational logic under proper modules
+        const bool isComb = !nodep->sensesp()
+                            && (nodep->keyword() == VAlwaysKwd::ALWAYS
+                                || nodep->keyword() == VAlwaysKwd::ALWAYS_COMB
+                                || nodep->keyword() == VAlwaysKwd::ALWAYS_LATCH);
+        m_candidatesp
+            = isComb && VN_IS(m_modp, Module) ? &m_extractionCandidates(m_modp) : nullptr;
+        iterateChildrenConst(nodep);
+    }
+
+    void visit(AstAssignW* nodep) override {
+        // Mark LHS variable as combinationally driven
+        if (AstVarRef* const vrefp = VN_CAST(nodep->lhsp(), VarRef)) vrefp->varp()->user4(true);
+        //
+        iterateChildrenConst(nodep);
+    }
+
+    void visit(AstAssign* nodep) override {
+        iterateExtractionCandidate(nodep->rhsp());
+        iterate(nodep->lhsp());
+    }
+
+    void visit(AstAssignDly* nodep) override {
+        iterateExtractionCandidate(nodep->rhsp());
+        iterate(nodep->lhsp());
+    }
+
+    void visit(AstIf* nodep) override {
+        iterateExtractionCandidate(nodep->condp());
+        iterateAndNextConstNull(nodep->thensp());
+        iterateAndNextConstNull(nodep->elsesp());
+    }
+
+    void visit(AstAssignForce* nodep) override {
+        iterate(nodep->rhsp());
+        UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest");
+        m_inForceReleaseLhs = true;
+        iterate(nodep->lhsp());
+        m_inForceReleaseLhs = false;
+    }
+
+    void visit(AstRelease* nodep) override {
+        UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest");
+        m_inForceReleaseLhs = true;
+        iterate(nodep->lhsp());
+        m_inForceReleaseLhs = false;
+    }
+
+    void visit(AstNodeMath* nodep) override { iterateChildrenConst(nodep); }
+
+    void visit(AstNodeVarRef* nodep) override {
+        if (nodep->access().isWriteOrRW()) {
+            // If it writes a variable, mark as impure
+            m_impure = true;
+            // Mark target of force/release
+            if (m_inForceReleaseLhs) nodep->varp()->user3(true);
+        } else {
+            // Otherwise, add read reference
+            m_readVars.push_back(nodep->varp());
+        }
+    }
+
+    void visit(AstNode* nodep) override {
+        // Conservatively assume unhandled nodes are impure. This covers all AstNodeFTaskRef
+        // as AstNodeFTaskRef are sadly not AstNodeMath.
+        m_impure = true;
+        // Still need to gather all references/force/release, etc.
+        iterateChildrenConst(nodep);
+    }
+
+    // CONSTRUCTOR
+    explicit DataflowExtractVisitor(AstNetlist* netlistp) { iterate(netlistp); }
+
+public:
+    static void apply(AstNetlist* netlistp) { DataflowExtractVisitor{netlistp}; }
+};
+
+void V3DfgOptimizer::extract(AstNetlist* netlistp) {
+    UINFO(2, __FUNCTION__ << ": " << endl);
+    // Extract more optimization candidates
+    DataflowExtractVisitor::apply(netlistp);
+    V3Global::dumpCheckGlobalTree("dfg-extract", 0, dumpTree() >= 3);
+}
+
+void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) {
+    UINFO(2, __FUNCTION__ << ": " << endl);
+
+    // NODE STATE
+    // AstVar::user1        -> Used by V3DfgPasses::astToDfg
+    // AstVar::user2        -> bool: Flag indicating referenced by AstVarXRef
+    const VNUser2InUse user2InUse;
+
+    // Mark cross-referenced variables
+    netlistp->foreach([](const AstVarXRef* xrefp) { xrefp->varp()->user2(true); });
+
+    V3DfgOptimizationContext ctx{label};
+
+    // Run the optimization phase
+    for (AstNode* nodep = netlistp->modulesp(); nodep; nodep = nodep->nextp()) {
+        // Only optimize proper modules
+        AstModule* const modp = VN_CAST(nodep, Module);
+        if (!modp) continue;
+
+        UINFO(4, "Applying DFG optimization to module '" << modp->name() << "'" << endl);
+        ++ctx.m_modules;
+
+        // Build the DFG of this module
+        const std::unique_ptr dfg{V3DfgPasses::astToDfg(*modp, ctx)};
+        if (dumpDfg() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-input");
+
+        // Extract the cyclic sub-graphs. We do this because a lot of the optimizations assume a
+        // DAG, and large, mostly acyclic graphs could not be optimized due to the presence of
+        // small cycles.
+        const std::vector>& cyclicComponents
+            = dfg->extractCyclicComponents("cyclic");
+
+        // Split the remaining acyclic DFG into [weakly] connected components
+        const std::vector>& acyclicComponents
+            = dfg->splitIntoComponents("acyclic");
+
+        // Quick sanity check
+        UASSERT_OBJ(dfg->size() == 0, nodep, "DfgGraph should have become empty");
+
+        // For each cyclic component
+        for (auto& component : cyclicComponents) {
+            if (dumpDfg() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source");
+            // TODO: Apply optimizations safe for cyclic graphs
+            // Add back under the main DFG (we will convert everything back in one go)
+            dfg->addGraph(*component);
+        }
+
+        // For each acyclic component
+        for (auto& component : acyclicComponents) {
+            if (dumpDfg() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source");
+            // Optimize the component
+            V3DfgPasses::optimize(*component, ctx);
+            // Add back under the main DFG (we will convert everything back in one go)
+            dfg->addGraph(*component);
+        }
+
+        // Convert back to Ast
+        if (dumpDfg() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized");
+        AstModule* const resultModp = V3DfgPasses::dfgToAst(*dfg, ctx);
+        UASSERT_OBJ(resultModp == modp, modp, "Should be the same module");
+    }
+    V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTree() >= 3);
+}
diff --git a/src/V3Changed.h b/src/V3DfgOptimizer.h
similarity index 65%
rename from src/V3Changed.h
rename to src/V3DfgOptimizer.h
index f9afbec85..5377dcd23 100644
--- a/src/V3Changed.h
+++ b/src/V3DfgOptimizer.h
@@ -1,6 +1,6 @@
 // -*- mode: C++; c-file-style: "cc-mode" -*-
 //*************************************************************************
-// DESCRIPTION: Verilator: Pre C-Emit stage changes
+// DESCRIPTION: Verilator: Dataflow based optimization of combinational logic
 //
 // Code available from: https://verilator.org
 //
@@ -14,19 +14,22 @@
 //
 //*************************************************************************
 
-#ifndef VERILATOR_V3CHANGED_H_
-#define VERILATOR_V3CHANGED_H_
+#ifndef VERILATOR_V3DFGOPTIMIZER_H_
+#define VERILATOR_V3DFGOPTIMIZER_H_
 
 #include "config_build.h"
 #include "verilatedos.h"
 
-class AstNetlist;
+#include "V3Ast.h"
 
 //============================================================================
 
-class V3Changed final {
-public:
-    static void changedAll(AstNetlist* nodep);
-};
+namespace V3DfgOptimizer {
+// Extract further logic blocks from the design for additional optimization opportunities
+void extract(AstNetlist*);
+
+// Optimize the design
+void optimize(AstNetlist*, const string& label);
+}  // namespace V3DfgOptimizer
 
 #endif  // Guard
diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp
new file mode 100644
index 000000000..2abc21986
--- /dev/null
+++ b/src/V3DfgPasses.cpp
@@ -0,0 +1,272 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+
+#include "config_build.h"
+
+#include "V3DfgPasses.h"
+
+#include "V3Dfg.h"
+#include "V3Global.h"
+#include "V3String.h"
+
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+V3DfgCseContext::~V3DfgCseContext() {
+    V3Stats::addStat("Optimizations, DFG " + m_label + " CSE, expressions eliminated",
+                     m_eliminated);
+}
+
+DfgRemoveVarsContext::~DfgRemoveVarsContext() {
+    V3Stats::addStat("Optimizations, DFG " + m_label + " Remove vars, variables removed",
+                     m_removed);
+}
+
+static std::string getPrefix(const std::string& label) {
+    if (label.empty()) return "";
+    std::string str = VString::removeWhitespace(label);
+    std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) {  //
+        return c == ' ' ? '-' : std::tolower(c);
+    });
+    str += "-";
+    return str;
+}
+
+V3DfgOptimizationContext::V3DfgOptimizationContext(const std::string& label)
+    : m_label{label}
+    , m_prefix{getPrefix(label)} {}
+
+V3DfgOptimizationContext::~V3DfgOptimizationContext() {
+    const string prefix = "Optimizations, DFG " + m_label + " ";
+    V3Stats::addStat(prefix + "General, modules", m_modules);
+    V3Stats::addStat(prefix + "Ast2Dfg, coalesced assignments", m_coalescedAssignments);
+    V3Stats::addStat(prefix + "Ast2Dfg, input equations", m_inputEquations);
+    V3Stats::addStat(prefix + "Ast2Dfg, representable", m_representable);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (dtype)", m_nonRepDType);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (impure)", m_nonRepImpure);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (timing)", m_nonRepTiming);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (lhs)", m_nonRepLhs);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (node)", m_nonRepNode);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (unknown)", m_nonRepUnknown);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (var ref)", m_nonRepVarRef);
+    V3Stats::addStat(prefix + "Ast2Dfg, non-representable (width)", m_nonRepWidth);
+    V3Stats::addStat(prefix + "Dfg2Ast, intermediate variables", m_intermediateVars);
+    V3Stats::addStat(prefix + "Dfg2Ast, replaced variables", m_replacedVars);
+    V3Stats::addStat(prefix + "Dfg2Ast, result equations", m_resultEquations);
+
+    // Check the stats are consistent
+    UASSERT(m_inputEquations
+                == m_representable + m_nonRepDType + m_nonRepImpure + m_nonRepTiming + m_nonRepLhs
+                       + m_nonRepNode + m_nonRepUnknown + m_nonRepVarRef + m_nonRepWidth,
+            "Inconsistent statistics");
+}
+
+// Common subexpression elimination
+void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
+    DfgVertex::EqualsCache equalsCache;
+    std::unordered_map> verticesWithEqualHashes;
+    verticesWithEqualHashes.reserve(dfg.size());
+
+    // Used by DfgVertex::hash
+    const auto userDataInUse = dfg.userDataInUse();
+
+    // Pre-hash variables for speed, these are all unique, so just set their hash to a unique value
+    uint32_t varHash = 0;
+    for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        vtxp->user() = V3Hash{++varHash};
+    }
+
+    // Similarly pre-hash constants for speed. While we don't combine constants, we do want
+    // expressions using the same constants to be combined, so we do need to hash equal constants
+    // to equal values.
+    for (DfgConst *vtxp = dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        // Get rid of unused constants while we are at it
+        if (!vtxp->hasSinks()) {
+            vtxp->unlinkDelete(dfg);
+            continue;
+        }
+        vtxp->user() = vtxp->num().toHash();
+    }
+
+    // Combine operation vertices
+    for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        // Get rid of unused operations while we are at it
+        if (!vtxp->hasSinks()) {
+            vtxp->unlinkDelete(dfg);
+            continue;
+        }
+        const V3Hash hash = vtxp->hash();
+        if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp);
+        std::vector& vec = verticesWithEqualHashes[hash];
+        bool replaced = false;
+        for (DfgVertex* const candidatep : vec) {
+            if (candidatep->equals(*vtxp, equalsCache)) {
+                ++ctx.m_eliminated;
+                vtxp->replaceWith(candidatep);
+                vtxp->unlinkDelete(dfg);
+                replaced = true;
+                break;
+            }
+        }
+        if (replaced) continue;
+        vec.push_back(vtxp);
+    }
+}
+
+void V3DfgPasses::inlineVars(DfgGraph& dfg) {
+    for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        if (DfgVarPacked* const varp = vtxp->cast()) {
+            // Don't inline SystemC variables, as SystemC types are not interchangeable with
+            // internal types, and hence the variables are not interchangeable either.
+            if (varp->hasSinks() && varp->isDrivenFullyByDfg() && !varp->varp()->isSc()) {
+                DfgVertex* const driverp = varp->source(0);
+
+                // If driven from a SystemC variable, don't inline this variable
+                if (DfgVertexVar* const driverVarp = driverp->cast()) {
+                    if (driverVarp->varp()->isSc()) continue;
+                }
+
+                varp->forEachSinkEdge([=](DfgEdge& edge) {
+                    // If sink is a SystemC variable, don't inline that sink
+                    if (DfgVertexVar* const sinkVarp = edge.sinkp()->cast()) {
+                        if (sinkVarp->varp()->isSc()) return;
+                    }
+                    edge.relinkSource(driverp);
+                });
+            }
+        }
+    }
+}
+
+void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) {
+    for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+
+        // We can only eliminate DfgVarPacked vertices at the moment
+        DfgVarPacked* const varp = vtxp->cast();
+        if (!varp) continue;
+
+        // Can't remove if it has consumers
+        if (varp->hasSinks()) continue;
+
+        // Can't remove if read in the module and driven here (i.e.: it's an output of the DFG)
+        if (varp->hasModRefs() && varp->isDrivenByDfg()) continue;
+
+        // Can't remove if only partially driven by the DFG
+        if (varp->isDrivenByDfg() && !varp->isDrivenFullyByDfg()) continue;
+
+        // Can't remove if referenced externally, or other special reasons
+        if (varp->keep()) continue;
+
+        // If the driver of this variable has multiple non-variable sinks, then we would need
+        // a temporary when rendering the graph. Instead of introducing a temporary, keep the
+        // first variable that is driven by that driver
+        if (varp->isDrivenByDfg()) {
+            DfgVertex* const driverp = varp->source(0);
+            unsigned nonVarSinks = 0;
+            const DfgVarPacked* firstSinkVarp = nullptr;
+            const bool keepFirst = driverp->findSink([&](const DfgVertex& sink) {
+                if (const DfgVarPacked* const sinkVarp = sink.cast()) {
+                    if (!firstSinkVarp) firstSinkVarp = sinkVarp;
+                } else {
+                    ++nonVarSinks;
+                }
+                // We can stop as soon as we found the first var, and 2 non-var sinks
+                return firstSinkVarp && nonVarSinks >= 2;
+            });
+            // Keep this DfgVarPacked if needed
+            if (keepFirst && firstSinkVarp == varp) continue;
+        }
+
+        // OK, we can delete this DfgVarPacked
+        ++ctx.m_removed;
+
+        // If not referenced outside the DFG, then also delete the referenced AstVar,
+        // as it is now unused.
+        if (!varp->hasRefs()) varp->varp()->unlinkFrBack()->deleteTree();
+
+        // Unlink and delete vertex
+        varp->unlinkDelete(dfg);
+    }
+}
+
+void V3DfgPasses::removeUnused(DfgGraph& dfg) {
+    // Iteratively remove operation vertices
+    while (true) {
+        // Do one pass over the graph.
+        bool changed = false;
+        for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNext();
+            if (!vtxp->hasSinks()) {
+                changed = true;
+                vtxp->unlinkDelete(dfg);
+            }
+        }
+        if (!changed) break;
+        // Do another pass in the opposite direction. Alternating directions reduces
+        // the pathological complexity with left/right leaning trees.
+        changed = false;
+        for (DfgVertex *vtxp = dfg.opVerticesRbeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesPrev();
+            if (!vtxp->hasSinks()) {
+                changed = true;
+                vtxp->unlinkDelete(dfg);
+            }
+        }
+        if (!changed) break;
+    }
+
+    // Finally remove unused constants
+    for (DfgConst *vtxp = dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNext();
+        if (!vtxp->hasSinks()) vtxp->unlinkDelete(dfg);
+    }
+}
+
+void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
+    // There is absolutely nothing useful we can do with a graph of size 2 or less
+    if (dfg.size() <= 2) return;
+
+    int passNumber = 0;
+
+    const auto apply = [&](int dumpLevel, const string name, std::function pass) {
+        pass();
+        if (dumpDfg() >= dumpLevel) {
+            const string strippedName = VString::removeWhitespace(name);
+            const string label
+                = ctx.prefix() + "pass-" + cvtToStr(passNumber) + "-" + strippedName;
+            dfg.dumpDotFilePrefixed(label);
+        }
+        ++passNumber;
+    };
+
+    if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input");
+    apply(3, "input         ", [&]() {});
+    apply(4, "cse           ", [&]() { cse(dfg, ctx.m_cseContext0); });
+    apply(4, "inlineVars    ", [&]() { inlineVars(dfg); });
+    if (v3Global.opt.fDfgPeephole()) {
+        apply(4, "peephole      ", [&]() { peephole(dfg, ctx.m_peepholeContext); });
+    }
+    apply(4, "cse           ", [&]() { cse(dfg, ctx.m_cseContext1); });
+    apply(4, "removeVars    ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); });
+    apply(3, "optimized     ", [&]() { removeUnused(dfg); });
+    if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized");
+}
diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h
new file mode 100644
index 000000000..93c0d94fa
--- /dev/null
+++ b/src/V3DfgPasses.h
@@ -0,0 +1,115 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Passes over DfgGraph
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3DFGPASSES_H_
+#define VERILATOR_V3DFGPASSES_H_
+
+#include "config_build.h"
+
+#include "V3DfgPeephole.h"
+
+class AstModule;
+class DfgGraph;
+
+//===========================================================================
+// Various context objects hold data that need to persist across invocations
+// of a DFG pass.
+
+class V3DfgCseContext final {
+    const std::string m_label;  // Label to apply to stats
+
+public:
+    VDouble0 m_eliminated;  // Number of common sub-expressions eliminated
+    explicit V3DfgCseContext(const std::string& label)
+        : m_label{label} {}
+    ~V3DfgCseContext();
+};
+
+class DfgRemoveVarsContext final {
+    const std::string m_label;  // Label to apply to stats
+
+public:
+    VDouble0 m_removed;  // Number of redundant variables removed
+    explicit DfgRemoveVarsContext(const std::string& label)
+        : m_label{label} {}
+    ~DfgRemoveVarsContext();
+};
+
+class V3DfgOptimizationContext final {
+    const std::string m_label;  // Label to add to stats, etc.
+    const std::string m_prefix;  // Prefix to add to file dumps (derived from label)
+
+public:
+    VDouble0 m_modules;  // Number of modules optimized
+    VDouble0 m_coalescedAssignments;  // Number of partial assignments coalesced
+    VDouble0 m_inputEquations;  // Number of input combinational equations
+    VDouble0 m_representable;  // Number of combinational equations representable
+    VDouble0 m_nonRepDType;  // Equations non-representable due to data type
+    VDouble0 m_nonRepImpure;  // Equations non-representable due to impure node
+    VDouble0 m_nonRepTiming;  // Equations non-representable due to timing control
+    VDouble0 m_nonRepLhs;  // Equations non-representable due to lhs
+    VDouble0 m_nonRepNode;  // Equations non-representable due to node type
+    VDouble0 m_nonRepUnknown;  // Equations non-representable due to unknown node
+    VDouble0 m_nonRepVarRef;  // Equations non-representable due to variable reference
+    VDouble0 m_nonRepWidth;  // Equations non-representable due to width mismatch
+    VDouble0 m_intermediateVars;  // Number of intermediate variables introduced
+    VDouble0 m_replacedVars;  // Number of variables replaced
+    VDouble0 m_resultEquations;  // Number of result combinational equations
+
+    V3DfgCseContext m_cseContext0{m_label + " 1st"};
+    V3DfgCseContext m_cseContext1{m_label + " 2nd"};
+    V3DfgPeepholeContext m_peepholeContext{m_label};
+    DfgRemoveVarsContext m_removeVarsContext{m_label};
+    explicit V3DfgOptimizationContext(const std::string& label);
+    ~V3DfgOptimizationContext();
+
+    const std::string& prefix() const { return m_prefix; }
+};
+
+namespace V3DfgPasses {
+//===========================================================================
+// Top level entry points
+//===========================================================================
+
+// Construct a DfGGraph representing the combinational logic in the given AstModule. The logic
+// that is represented by the graph is removed from the given AstModule. Returns the
+// constructed DfgGraph.
+DfgGraph* astToDfg(AstModule&, V3DfgOptimizationContext&);
+
+// Optimize the given DfgGraph
+void optimize(DfgGraph&, V3DfgOptimizationContext&);
+
+// Convert DfgGraph back into Ast, and insert converted graph back into its parent module.
+// Returns the parent module.
+AstModule* dfgToAst(DfgGraph&, V3DfgOptimizationContext&);
+
+//===========================================================================
+// Intermediate/internal operations
+//===========================================================================
+
+// Common subexpression elimination
+void cse(DfgGraph&, V3DfgCseContext&);
+// Inline fully driven variables
+void inlineVars(DfgGraph&);
+// Peephole optimizations
+void peephole(DfgGraph&, V3DfgPeepholeContext&);
+// Remove redundant variables
+void removeVars(DfgGraph&, DfgRemoveVarsContext&);
+// Remove unused nodes
+void removeUnused(DfgGraph&);
+}  // namespace V3DfgPasses
+
+#endif
diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp
new file mode 100644
index 000000000..bb0fe31b8
--- /dev/null
+++ b/src/V3DfgPeephole.cpp
@@ -0,0 +1,1594 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Peephole optimizations over DfgGraph
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// A pattern-matching based optimizer for DfgGraph. This is in some aspects similar to V3Const, but
+// more powerful in that it does not care about ordering combinational statement. This is also less
+// broadly applicable than V3Const, as it does not apply to procedural statements with sequential
+// execution semantics.
+//
+//*************************************************************************
+
+#include "config_build.h"
+
+#include "V3DfgPeephole.h"
+
+#include "V3Ast.h"
+#include "V3Dfg.h"
+#include "V3DfgPasses.h"
+#include "V3Stats.h"
+
+#include 
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+V3DfgPeepholeContext::V3DfgPeepholeContext(const std::string& label)
+    : m_label{label} {
+    const auto checkEnabled = [this](VDfgPeepholePattern id) {
+        string str{id.ascii()};
+        std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) {  //
+            return c == '_' ? '-' : std::tolower(c);
+        });
+        m_enabled[id] = v3Global.opt.fDfgPeepholeEnabled(str);
+    };
+#define OPTIMIZATION_CHECK_ENABLED(id, name) checkEnabled(VDfgPeepholePattern::id);
+    FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_CHECK_ENABLED)
+#undef OPTIMIZATION_CHECK_ENABLED
+}
+
+V3DfgPeepholeContext::~V3DfgPeepholeContext() {
+    const auto emitStat = [this](VDfgPeepholePattern id) {
+        string str{id.ascii()};
+        std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) {  //
+            return c == '_' ? ' ' : std::tolower(c);
+        });
+        V3Stats::addStat("Optimizations, DFG " + m_label + " Peephole, " + str, m_count[id]);
+    };
+#define OPTIMIZATION_EMIT_STATS(id, name) emitStat(VDfgPeepholePattern::id);
+    FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_EMIT_STATS)
+#undef OPTIMIZATION_EMIT_STATS
+}
+
+// clang-format off
+template 
+struct ReductionToBitwiseImpl {};
+template <> struct ReductionToBitwiseImpl { using type = DfgAnd; };
+template <> struct ReductionToBitwiseImpl  { using type = DfgOr;  };
+template <> struct ReductionToBitwiseImpl { using type = DfgXor; };
+template 
+using ReductionToBitwise = typename ReductionToBitwiseImpl::type;
+
+template 
+struct BitwiseToReductionImpl {};
+template <> struct BitwiseToReductionImpl { using type = DfgRedAnd; };
+template <> struct BitwiseToReductionImpl  { using type = DfgRedOr;  };
+template <> struct BitwiseToReductionImpl { using type = DfgRedXor; };
+template 
+using BitwiseToReduction = typename BitwiseToReductionImpl::type;
+
+namespace {
+template void foldOp(V3Number& out, const V3Number& src);
+template <> void foldOp      (V3Number& out, const V3Number& src) { out.opCLog2(src); }
+template <> void foldOp  (V3Number& out, const V3Number& src) { out.opCountOnes(src); }
+template <> void foldOp     (V3Number& out, const V3Number& src) { out.opAssign(src); }
+template <> void foldOp    (V3Number& out, const V3Number& src) { out.opExtendS(src, src.width()); }
+template <> void foldOp     (V3Number& out, const V3Number& src) { out.opLogNot(src); }
+template <> void foldOp     (V3Number& out, const V3Number& src) { out.opNegate(src); }
+template <> void foldOp        (V3Number& out, const V3Number& src) { out.opNot(src); }
+template <> void foldOp     (V3Number& out, const V3Number& src) { out.opOneHot(src); }
+template <> void foldOp    (V3Number& out, const V3Number& src) { out.opOneHot0(src); }
+template <> void foldOp     (V3Number& out, const V3Number& src) { out.opRedAnd(src); }
+template <> void foldOp      (V3Number& out, const V3Number& src) { out.opRedOr(src); }
+template <> void foldOp     (V3Number& out, const V3Number& src) { out.opRedXor(src); }
+
+template void foldOp(V3Number& out, const V3Number& lhs, const V3Number& rhs);
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opAdd(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opAnd(lhs, rhs); }
+template <> void foldOp     (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opConcat(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opDiv(lhs, rhs); }
+template <> void foldOp       (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opDivS(lhs, rhs); }
+template <> void foldOp         (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opEq(lhs, rhs); }
+template <> void foldOp         (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGt(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGtS(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGte(lhs, rhs); }
+template <> void foldOp       (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGteS(lhs, rhs); }
+template <> void foldOp     (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogAnd(lhs, rhs); }
+template <> void foldOp      (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogEq(lhs, rhs); }
+template <> void foldOp      (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogIf(lhs, rhs); }
+template <> void foldOp      (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogOr(lhs, rhs); }
+template <> void foldOp         (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLt(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLtS(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLte(lhs, rhs); }
+template <> void foldOp       (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLtS(lhs, rhs); }
+template <> void foldOp     (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opModDiv(lhs, rhs); }
+template <> void foldOp    (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opModDivS(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opMul(lhs, rhs); }
+template <> void foldOp       (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opMulS(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opNeq(lhs, rhs); }
+template <> void foldOp         (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opOr(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPow(lhs, rhs); }
+template <> void foldOp      (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowSS(lhs, rhs); }
+template <> void foldOp      (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowSU(lhs, rhs); }
+template <> void foldOp      (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowUS(lhs, rhs); }
+template <> void foldOp  (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opRepl(lhs, rhs); }
+template <> void foldOp     (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftL(lhs, rhs); }
+template <> void foldOp     (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftR(lhs, rhs); }
+template <> void foldOp    (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftRS(lhs, rhs, lhs.width()); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opSub(lhs, rhs); }
+template <> void foldOp        (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opXor(lhs, rhs); }
+}
+// clang-format on
+
+class V3DfgPeephole final : public DfgVisitor {
+
+    // STATE
+    DfgGraph& m_dfg;  // The DfgGraph being visited
+    V3DfgPeepholeContext& m_ctx;  // The config structure
+    bool m_changed = false;  // Changed a vertex
+    AstNodeDType* const m_bitDType = DfgVertex::dtypeForWidth(1);  // Common, so grab it up front
+
+#define APPLYING(id) if (checkApplying(VDfgPeepholePattern::id))
+
+    // METHODS
+    bool checkApplying(VDfgPeepholePattern id) {
+        if (!m_ctx.m_enabled[id]) return false;
+        UINFO(9, "Applying DFG patten " << id.ascii() << endl);
+        ++m_ctx.m_count[id];
+        m_changed = true;
+        return true;
+    }
+
+    // Shorthand
+    static AstNodeDType* dtypeForWidth(uint32_t width) { return DfgVertex::dtypeForWidth(width); }
+
+    // Create a 32-bit DfgConst vertex
+    DfgConst* makeI32(FileLine* flp, uint32_t val) { return new DfgConst{m_dfg, flp, 32, val}; }
+
+    // Create a DfgConst vertex with the given width and value zero
+    DfgConst* makeZero(FileLine* flp, uint32_t width) { return new DfgConst{m_dfg, flp, width}; }
+
+    // Constant fold unary vertex, return true if folded
+    template 
+    bool foldUnary(Vertex* vtxp) {
+        static_assert(std::is_base_of::value, "Must invoke on unary");
+        static_assert(std::is_final::value, "Must invoke on final class");
+        if (DfgConst* const srcp = vtxp->srcp()->template cast()) {
+            APPLYING(FOLD_UNARY) {
+                DfgConst* const resultp = makeZero(vtxp->fileline(), vtxp->width());
+                foldOp(resultp->num(), srcp->num());
+                vtxp->replaceWith(resultp);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Constant fold binary vertex, return true if folded
+    template 
+    bool foldBinary(Vertex* vtxp) {
+        static_assert(std::is_base_of::value, "Must invoke on binary");
+        static_assert(std::is_final::value, "Must invoke on final class");
+        if (DfgConst* const lhsp = vtxp->lhsp()->template cast()) {
+            if (DfgConst* const rhsp = vtxp->rhsp()->template cast()) {
+                APPLYING(FOLD_BINARY) {
+                    DfgConst* const resultp = makeZero(vtxp->fileline(), vtxp->width());
+                    foldOp(resultp->num(), lhsp->num(), rhsp->num());
+                    vtxp->replaceWith(resultp);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    // Rotate the expression tree rooted at 'vtxp' to the right ('vtxp->lhsp()' becomes root,
+    // producing a right-leaning tree). Warning: only valid for associative operations.
+    template 
+    void rotateRight(Vertex* vtxp) {
+        static_assert(std::is_base_of::value, "Must invoke on binary");
+        static_assert(std::is_final::value, "Must invoke on final class");
+        DfgVertexBinary* const ap = vtxp;
+        DfgVertexBinary* const bp = vtxp->lhsp()->template as();
+        UASSERT_OBJ(!bp->hasMultipleSinks(), vtxp, "Can't rotate a non-tree");
+        ap->replaceWith(bp);
+        ap->lhsp(bp->rhsp());
+        bp->rhsp(ap);
+        // Concatenation dtypes need to be fixed up, other associative nodes preserve types
+        if VL_CONSTEXPR_CXX17 (std::is_same::value) {
+            ap->dtypep(dtypeForWidth(ap->lhsp()->width() + ap->rhsp()->width()));
+            bp->dtypep(dtypeForWidth(bp->lhsp()->width() + bp->rhsp()->width()));
+        }
+    }
+
+    // Transformations that apply to all associative binary vertices.
+    // Returns true if vtxp was replaced.
+    template 
+    bool associativeBinary(Vertex* vtxp) {
+        static_assert(std::is_base_of::value, "Must invoke on binary");
+        static_assert(std::is_final::value, "Must invoke on final class");
+
+        DfgVertex* const lhsp = vtxp->lhsp();
+        DfgVertex* const rhsp = vtxp->rhsp();
+        FileLine* const flp = vtxp->fileline();
+
+        DfgConst* const lConstp = lhsp->cast();
+        DfgConst* const rConstp = rhsp->cast();
+
+        if (lConstp && rConstp) {
+            APPLYING(FOLD_ASSOC_BINARY) {
+                DfgConst* const resultp = makeZero(flp, vtxp->width());
+                foldOp(resultp->num(), lConstp->num(), rConstp->num());
+                vtxp->replaceWith(resultp);
+                return true;
+            }
+        }
+
+        if (lConstp) {
+            if (Vertex* const rVtxp = rhsp->cast()) {
+                if (DfgConst* const rlConstp = rVtxp->lhsp()->template cast()) {
+                    APPLYING(FOLD_ASSOC_BINARY_LHS_OF_RHS) {
+                        // Fold constants
+                        const uint32_t width = std::is_same::value
+                                                   ? lConstp->width() + rlConstp->width()
+                                                   : vtxp->width();
+                        DfgConst* const constp = makeZero(flp, width);
+                        foldOp(constp->num(), lConstp->num(), rlConstp->num());
+
+                        // Replace vertex
+                        if (!rVtxp->hasMultipleSinks()) {
+                            rVtxp->lhsp(constp);
+                            rVtxp->dtypep(vtxp->dtypep());
+                            vtxp->replaceWith(rVtxp);
+                        } else {
+                            Vertex* const resp = new Vertex{m_dfg, flp, vtxp->dtypep()};
+                            resp->lhsp(constp);
+                            resp->rhsp(rVtxp->rhsp());
+                            vtxp->replaceWith(resp);
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+
+        if (rConstp) {
+            if (Vertex* const lVtxp = lhsp->cast()) {
+                if (DfgConst* const lrConstp = lVtxp->rhsp()->template cast()) {
+                    APPLYING(FOLD_ASSOC_BINARY_RHS_OF_LHS) {
+                        // Fold constants
+                        const uint32_t width = std::is_same::value
+                                                   ? lrConstp->width() + rConstp->width()
+                                                   : vtxp->width();
+                        DfgConst* const constp = makeZero(flp, width);
+                        foldOp(constp->num(), lrConstp->num(), rConstp->num());
+
+                        // Replace vertex
+                        if (!lVtxp->hasMultipleSinks()) {
+                            lVtxp->rhsp(constp);
+                            lVtxp->dtypep(vtxp->dtypep());
+                            vtxp->replaceWith(lVtxp);
+                        } else {
+                            Vertex* const resp = new Vertex{m_dfg, flp, vtxp->dtypep()};
+                            resp->lhsp(lVtxp->lhsp());
+                            resp->rhsp(constp);
+                            vtxp->replaceWith(resp);
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // Make associative trees right leaning to reduce pattern variations, and for better CSE
+        while (vtxp->lhsp()->template is() && !vtxp->lhsp()->hasMultipleSinks()) {
+            APPLYING(RIGHT_LEANING_ASSOC) {
+                rotateRight(vtxp);
+                continue;
+            }
+            break;
+        }
+
+        return false;
+    }
+
+    // Transformations that apply to all commutative binary vertices
+    void commutativeBinary(DfgVertexBinary* vtxp) {
+        DfgVertex* const lhsp = vtxp->source<0>();
+        DfgVertex* const rhsp = vtxp->source<1>();
+        // Ensure Const is on left-hand side to simplify other patterns
+        if (lhsp->is()) return;
+        if (rhsp->is()) {
+            APPLYING(SWAP_CONST_IN_COMMUTATIVE_BINARY) {
+                vtxp->lhsp(rhsp);
+                vtxp->rhsp(lhsp);
+                return;
+            }
+        }
+        // Ensure Not is on the left-hand side to simplify other patterns
+        if (lhsp->is()) return;
+        if (rhsp->is()) {
+            APPLYING(SWAP_NOT_IN_COMMUTATIVE_BINARY) {
+                vtxp->lhsp(rhsp);
+                vtxp->rhsp(lhsp);
+                return;
+            }
+        }
+        // If both sides are variable references, order the side in some defined way. This allows
+        // CSE to later merge 'a op b' with 'b op a'.
+        if (lhsp->is() && rhsp->is()) {
+            AstVar* const lVarp = lhsp->as()->varp();
+            AstVar* const rVarp = rhsp->as()->varp();
+            if (lVarp->name() > rVarp->name()) {
+                APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) {
+                    vtxp->lhsp(rhsp);
+                    vtxp->rhsp(lhsp);
+                    return;
+                }
+            }
+        }
+    }
+
+    // Bitwise operation with one side Const, and the other side a Concat
+    template 
+    bool tryPushBitwiseOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) {
+        UASSERT_OBJ(constp->dtypep() == concatp->dtypep(), vtxp, "Mismatched widths");
+
+        FileLine* const flp = vtxp->fileline();
+
+        // If at least one of the sides of the Concat constant, or width 1 (i.e.: can be
+        // further simplified), then push the Vertex past the Concat
+        if (concatp->lhsp()->is() || concatp->rhsp()->is()  //
+            || concatp->lhsp()->dtypep() == m_bitDType
+            || concatp->rhsp()->dtypep() == m_bitDType) {
+            APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) {
+                const uint32_t width = concatp->width();
+                AstNodeDType* const lDtypep = concatp->lhsp()->dtypep();
+                AstNodeDType* const rDtypep = concatp->rhsp()->dtypep();
+                const uint32_t lWidth = lDtypep->width();
+                const uint32_t rWidth = rDtypep->width();
+
+                // The new Lhs vertex
+                Vertex* const newLhsp = new Vertex{m_dfg, flp, lDtypep};
+                DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth);
+                newLhsConstp->num().opSel(constp->num(), width - 1, rWidth);
+                newLhsp->lhsp(newLhsConstp);
+                newLhsp->rhsp(concatp->lhsp());
+
+                // The new Rhs vertex
+                Vertex* const newRhsp = new Vertex{m_dfg, flp, rDtypep};
+                DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth);
+                newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0);
+                newRhsp->lhsp(newRhsConstp);
+                newRhsp->rhsp(concatp->rhsp());
+
+                // The replacement Concat vertex
+                DfgConcat* const newConcat
+                    = new DfgConcat{m_dfg, concatp->fileline(), concatp->dtypep()};
+                newConcat->lhsp(newLhsp);
+                newConcat->rhsp(newRhsp);
+
+                // Replace this vertex
+                vtxp->replaceWith(newConcat);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    template 
+    bool tryPushCompareOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) {
+        UASSERT_OBJ(constp->dtypep() == concatp->dtypep(), vtxp, "Mismatched widths");
+
+        FileLine* const flp = vtxp->fileline();
+
+        // If at least one of the sides of the Concat is constant, then push the Vertex past the
+        // Concat
+        if (concatp->lhsp()->is() || concatp->rhsp()->is()) {
+            APPLYING(PUSH_COMPARE_OP_THROUGH_CONCAT) {
+                const uint32_t width = concatp->width();
+                const uint32_t lWidth = concatp->lhsp()->width();
+                const uint32_t rWidth = concatp->rhsp()->width();
+
+                // The new Lhs vertex
+                Vertex* const newLhsp = new Vertex{m_dfg, flp, m_bitDType};
+                DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth);
+                newLhsConstp->num().opSel(constp->num(), width - 1, rWidth);
+                newLhsp->lhsp(newLhsConstp);
+                newLhsp->rhsp(concatp->lhsp());
+
+                // The new Rhs vertex
+                Vertex* const newRhsp = new Vertex{m_dfg, flp, m_bitDType};
+                DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth);
+                newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0);
+                newRhsp->lhsp(newRhsConstp);
+                newRhsp->rhsp(concatp->rhsp());
+
+                // The replacement Vertex
+                DfgVertexBinary* const replacementp
+                    = std::is_same::value
+                          ? new DfgAnd{m_dfg, concatp->fileline(), m_bitDType}
+                          : nullptr;
+                UASSERT_OBJ(replacementp, vtxp,
+                            "Unhandled vertex type in 'tryPushCompareOpThroughConcat': "
+                                << vtxp->typeName());
+                replacementp->relinkSource<0>(newLhsp);
+                replacementp->relinkSource<1>(newRhsp);
+
+                // Replace this vertex
+                vtxp->replaceWith(replacementp);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    template 
+    bool tryPushBitwiseOpThroughReductions(Bitwise* vtxp) {
+        using Reduction = BitwiseToReduction;
+
+        if (Reduction* const lRedp = vtxp->lhsp()->template cast()) {
+            if (Reduction* const rRedp = vtxp->rhsp()->template cast()) {
+                DfgVertex* const lSrcp = lRedp->srcp();
+                DfgVertex* const rSrcp = rRedp->srcp();
+                if (lSrcp->dtypep() == rSrcp->dtypep() && lSrcp->width() <= 64
+                    && !lSrcp->hasMultipleSinks() && !rSrcp->hasMultipleSinks()) {
+                    APPLYING(PUSH_BITWISE_THROUGH_REDUCTION) {
+                        FileLine* const flp = vtxp->fileline();
+                        Bitwise* const bwp = new Bitwise{m_dfg, flp, lSrcp->dtypep()};
+                        bwp->lhsp(lSrcp);
+                        bwp->rhsp(rSrcp);
+                        Reduction* const redp = new Reduction{m_dfg, flp, m_bitDType};
+                        redp->srcp(bwp);
+                        vtxp->replaceWith(redp);
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    template 
+    void optimizeReduction(Reduction* vtxp) {
+        using Bitwise = ReductionToBitwise;
+
+        if (foldUnary(vtxp)) return;
+
+        DfgVertex* const srcp = vtxp->srcp();
+        FileLine* const flp = vtxp->fileline();
+
+        // Reduction of 1-bit value
+        if (srcp->dtypep() == m_bitDType) {
+            APPLYING(REMOVE_WIDTH_ONE_REDUCTION) {
+                vtxp->replaceWith(srcp);
+                return;
+            }
+        }
+
+        if (DfgCond* const condp = srcp->cast()) {
+            if (condp->thenp()->is() || condp->elsep()->is()) {
+                APPLYING(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) {
+                    // The new 'then' vertex
+                    Reduction* const newThenp = new Reduction{m_dfg, flp, m_bitDType};
+                    newThenp->srcp(condp->thenp());
+
+                    // The new 'else' vertex
+                    Reduction* const newElsep = new Reduction{m_dfg, flp, m_bitDType};
+                    newElsep->srcp(condp->elsep());
+
+                    // The replacement Cond vertex
+                    DfgCond* const newCondp = new DfgCond{m_dfg, condp->fileline(), m_bitDType};
+                    newCondp->condp(condp->condp());
+                    newCondp->thenp(newThenp);
+                    newCondp->elsep(newElsep);
+
+                    // Replace this vertex
+                    vtxp->replaceWith(newCondp);
+                    return;
+                }
+            }
+        }
+
+        if (DfgConcat* const concatp = srcp->cast()) {
+            if (concatp->lhsp()->is() || concatp->rhsp()->is()) {
+                APPLYING(PUSH_REDUCTION_THROUGH_CONCAT) {
+                    // Reduce the parts of the concatenation
+                    Reduction* const lRedp = new Reduction{m_dfg, concatp->fileline(), m_bitDType};
+                    lRedp->srcp(concatp->lhsp());
+                    Reduction* const rRedp = new Reduction{m_dfg, concatp->fileline(), m_bitDType};
+                    rRedp->srcp(concatp->rhsp());
+
+                    // Bitwise reduce the results
+                    Bitwise* const replacementp = new Bitwise{m_dfg, flp, m_bitDType};
+                    replacementp->lhsp(lRedp);
+                    replacementp->rhsp(rRedp);
+                    vtxp->replaceWith(replacementp);
+
+                    // Optimize the new terms
+                    optimizeReduction(lRedp);
+                    optimizeReduction(rRedp);
+                    iterate(replacementp);
+                    return;
+                }
+            }
+        }
+    }
+
+    void optimizeShiftRHS(DfgVertexBinary* vtxp) {
+        if (const DfgConcat* const concatp = vtxp->rhsp()->cast()) {
+            if (concatp->lhsp()->isZero()) {  // Drop redundant zero extension
+                APPLYING(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) {  //
+                    vtxp->rhsp(concatp->rhsp());
+                }
+            }
+        }
+    }
+
+    // VISIT methods
+
+    void visit(DfgVertex*) override {}
+
+    //=========================================================================
+    //  DfgVertexUnary
+    //=========================================================================
+
+    void visit(DfgCLog2* vtxp) override {
+        if (foldUnary(vtxp)) return;
+    }
+
+    void visit(DfgCountOnes* vtxp) override {
+        if (foldUnary(vtxp)) return;
+    }
+
+    void visit(DfgExtend* vtxp) override {
+        UASSERT_OBJ(vtxp->width() > vtxp->srcp()->width(), vtxp, "Invalid zero extend");
+
+        if (foldUnary(vtxp)) return;
+
+        // Convert all Extend into Concat with zeros. This simplifies other patterns as they only
+        // need to handle Concat, which is more generic, and don't need special cases for
+        // Extend.
+        APPLYING(REPLACE_EXTEND) {
+            FileLine* const flp = vtxp->fileline();
+            DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()};
+            replacementp->lhsp(makeZero(flp, vtxp->width() - vtxp->srcp()->width()));
+            replacementp->rhsp(vtxp->srcp());
+            vtxp->replaceWith(replacementp);
+        }
+    }
+
+    void visit(DfgExtendS* vtxp) override {
+        UASSERT_OBJ(vtxp->width() > vtxp->srcp()->width(), vtxp, "Invalid sign extend");
+
+        if (foldUnary(vtxp)) return;
+    }
+
+    void visit(DfgLogNot* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == m_bitDType, vtxp, "Incorrect width");
+
+        if (foldUnary(vtxp)) return;
+    }
+
+    void visit(DfgNegate* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->srcp()->dtypep(), vtxp, "Mismatched width");
+
+        if (foldUnary(vtxp)) return;
+    }
+
+    void visit(DfgNot* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->srcp()->dtypep(), vtxp, "Mismatched width");
+
+        if (foldUnary(vtxp)) return;
+
+        // Not of Cond
+        if (DfgCond* const condp = vtxp->srcp()->cast()) {
+            // If at least one of the branches are a constant, push the Not past the Cond
+            if (condp->thenp()->is() || condp->elsep()->is()) {
+                APPLYING(PUSH_NOT_THROUGH_COND) {
+                    // The new 'then' vertex
+                    DfgNot* const newThenp = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()};
+                    newThenp->srcp(condp->thenp());
+
+                    // The new 'else' vertex
+                    DfgNot* const newElsep = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()};
+                    newElsep->srcp(condp->elsep());
+
+                    // The replacement Cond vertex
+                    DfgCond* const newCondp
+                        = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()};
+                    newCondp->condp(condp->condp());
+                    newCondp->thenp(newThenp);
+                    newCondp->elsep(newElsep);
+
+                    // Replace this vertex
+                    vtxp->replaceWith(newCondp);
+                    return;
+                }
+            }
+        }
+
+        // Not of Not
+        if (DfgNot* const notp = vtxp->srcp()->cast()) {
+            UASSERT_OBJ(vtxp->dtypep() == notp->srcp()->dtypep(), vtxp, "Width mismatch");
+            APPLYING(REMOVE_NOT_NOT) {
+                vtxp->replaceWith(notp->srcp());
+                return;
+            }
+        }
+
+        if (!vtxp->srcp()->hasMultipleSinks()) {
+            // Not of Eq
+            if (DfgEq* const eqp = vtxp->srcp()->cast()) {
+                APPLYING(REPLACE_NOT_EQ) {
+                    DfgNeq* const replacementp
+                        = new DfgNeq{m_dfg, eqp->fileline(), vtxp->dtypep()};
+                    replacementp->lhsp(eqp->lhsp());
+                    replacementp->rhsp(eqp->rhsp());
+                    vtxp->replaceWith(replacementp);
+                    return;
+                }
+            }
+
+            // Not of Neq
+            if (DfgNeq* const neqp = vtxp->srcp()->cast()) {
+                APPLYING(REPLACE_NOT_NEQ) {
+                    DfgEq* const replacementp = new DfgEq{m_dfg, neqp->fileline(), vtxp->dtypep()};
+                    replacementp->lhsp(neqp->lhsp());
+                    replacementp->rhsp(neqp->rhsp());
+                    vtxp->replaceWith(replacementp);
+                    return;
+                }
+            }
+        }
+    }
+
+    void visit(DfgOneHot* vtxp) override {
+        if (foldUnary(vtxp)) return;
+    }
+
+    void visit(DfgOneHot0* vtxp) override {
+        if (foldUnary(vtxp)) return;
+    }
+
+    void visit(DfgRedOr* vtxp) override { optimizeReduction(vtxp); }
+
+    void visit(DfgRedAnd* vtxp) override { optimizeReduction(vtxp); }
+
+    void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); }
+
+    void visit(DfgSel* vtxp) override {
+        DfgVertex* const fromp = vtxp->fromp();
+
+        FileLine* const flp = vtxp->fileline();
+
+        const uint32_t lsb = vtxp->lsb();
+        const uint32_t width = vtxp->width();
+        const uint32_t msb = lsb + width - 1;
+
+        if (DfgConst* const constp = fromp->cast()) {
+            APPLYING(FOLD_SEL) {
+                DfgConst* const replacementp = makeZero(flp, width);
+                replacementp->num().opSel(constp->num(), msb, lsb);
+                vtxp->replaceWith(replacementp);
+                return;
+            }
+        }
+
+        // Full width select, replace with the source.
+        if (fromp->width() == width) {
+            UASSERT_OBJ(lsb == 0, fromp, "OOPS");
+            APPLYING(REMOVE_FULL_WIDTH_SEL) {
+                vtxp->replaceWith(fromp);
+                return;
+            }
+        }
+
+        // Sel from Concat
+        if (DfgConcat* const concatp = fromp->cast()) {
+            DfgVertex* const lhsp = concatp->lhsp();
+            DfgVertex* const rhsp = concatp->rhsp();
+
+            if (msb < rhsp->width()) {
+                // If the select is entirely from rhs, then replace with sel from rhs
+                APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) {  //
+                    vtxp->fromp(rhsp);
+                }
+            } else if (lsb >= rhsp->width()) {
+                // If the select is entirely from the lhs, then replace with sel from lhs
+                APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) {
+                    vtxp->fromp(lhsp);
+                    vtxp->lsb(lsb - rhsp->width());
+                }
+            } else if (lsb == 0 || msb == concatp->width() - 1  //
+                       || lhsp->is() || rhsp->is()  //
+                       || !concatp->hasMultipleSinks()) {
+                // If the select straddles both sides, but at least one of the sides is wholly
+                // selected, or at least one of the sides is a Const, or this concat has no other
+                // use, then push the Sel past the Concat
+                APPLYING(PUSH_SEL_THROUGH_CONCAT) {
+                    const uint32_t rSelWidth = rhsp->width() - lsb;
+                    const uint32_t lSelWidth = width - rSelWidth;
+
+                    // The new Lhs vertex
+                    DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)};
+                    newLhsp->fromp(lhsp);
+                    newLhsp->lsb(0);
+
+                    // The new Rhs vertex
+                    DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)};
+                    newRhsp->fromp(rhsp);
+                    newRhsp->lsb(lsb);
+
+                    // The replacement Concat vertex
+                    DfgConcat* const newConcat
+                        = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()};
+                    newConcat->lhsp(newLhsp);
+                    newConcat->rhsp(newRhsp);
+
+                    // Replace this vertex
+                    vtxp->replaceWith(newConcat);
+                    return;
+                }
+            }
+        }
+
+        if (DfgReplicate* const repp = fromp->cast()) {
+            // If the Sel is wholly into the source of the Replicate, push the Sel through the
+            // Replicate and apply it directly to the source of the Replicate.
+            const uint32_t srcWidth = repp->srcp()->width();
+            if (width <= srcWidth) {
+                const uint32_t newLsb = lsb % srcWidth;
+                if (newLsb + width <= srcWidth) {
+                    APPLYING(PUSH_SEL_THROUGH_REPLICATE) {
+                        vtxp->fromp(repp->srcp());
+                        vtxp->lsb(newLsb);
+                    }
+                }
+            }
+        }
+
+        // Sel from Not
+        if (DfgNot* const notp = fromp->cast()) {
+            // Replace "Sel from Not" with "Not of Sel"
+            if (!notp->hasMultipleSinks()) {
+                UASSERT_OBJ(notp->srcp()->dtypep() == notp->dtypep(), notp, "Mismatched widths");
+                APPLYING(PUSH_SEL_THROUGH_NOT) {
+                    // Make Sel select from source of Not
+                    vtxp->fromp(notp->srcp());
+                    // Add Not after Sel
+                    DfgNot* const replacementp
+                        = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()};
+                    vtxp->replaceWith(replacementp);
+                    replacementp->srcp(vtxp);
+                }
+            }
+        }
+
+        // Sel from Sel
+        if (DfgSel* const selp = fromp->cast()) {
+            APPLYING(REPLACE_SEL_FROM_SEL) {
+                // Make this Sel select from the source of the source Sel
+                vtxp->fromp(selp->fromp());
+                // Adjust LSB
+                vtxp->lsb(lsb + selp->lsb());
+            }
+        }
+
+        // Sel from Cond
+        if (DfgCond* const condp = fromp->cast()) {
+            // If at least one of the branches are a constant, push the select past the cond
+            if (condp->thenp()->is() || condp->elsep()->is()) {
+                APPLYING(PUSH_SEL_THROUGH_COND) {
+                    // The new 'then' vertex
+                    DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()};
+                    newThenp->fromp(condp->thenp());
+                    newThenp->lsb(lsb);
+
+                    // The new 'else' vertex
+                    DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()};
+                    newElsep->fromp(condp->elsep());
+                    newElsep->lsb(lsb);
+
+                    // The replacement Cond vertex
+                    DfgCond* const newCondp
+                        = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()};
+                    newCondp->condp(condp->condp());
+                    newCondp->thenp(newThenp);
+                    newCondp->elsep(newElsep);
+
+                    // Replace this vertex
+                    vtxp->replaceWith(newCondp);
+                    return;
+                }
+            }
+        }
+
+        // Sel from ShiftL
+        if (DfgShiftL* const shiftLp = fromp->cast()) {
+            // If selecting bottom bits of left shift, push the Sel before the shift
+            if (lsb == 0) {
+                UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow");
+                APPLYING(PUSH_SEL_THROUGH_SHIFTL) {
+                    vtxp->fromp(shiftLp->lhsp());
+                    DfgShiftL* const newShiftLp
+                        = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()};
+                    vtxp->replaceWith(newShiftLp);
+                    newShiftLp->lhsp(vtxp);
+                    newShiftLp->rhsp(shiftLp->rhsp());
+                }
+            }
+        }
+    }
+
+    //=========================================================================
+    //  DfgVertexBinary - bitwise
+    //=========================================================================
+
+    void visit(DfgAnd* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width");
+
+        if (associativeBinary(vtxp)) return;
+
+        commutativeBinary(vtxp);
+
+        DfgVertex* const lhsp = vtxp->lhsp();
+        DfgVertex* const rhsp = vtxp->rhsp();
+        FileLine* const flp = vtxp->fileline();
+
+        // Bubble pushing
+        if (!vtxp->hasMultipleSinks() && !lhsp->hasMultipleSinks() && !rhsp->hasMultipleSinks()) {
+            if (DfgNot* const lhsNotp = lhsp->cast()) {
+                if (DfgNot* const rhsNotp = rhsp->cast()) {
+                    APPLYING(REPLACE_AND_OF_NOT_AND_NOT) {
+                        DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()};
+                        orp->lhsp(lhsNotp->srcp());
+                        orp->rhsp(rhsNotp->srcp());
+                        DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()};
+                        notp->srcp(orp);
+                        vtxp->replaceWith(notp);
+                        return;
+                    }
+                }
+                if (DfgNeq* const rhsNeqp = rhsp->cast()) {
+                    APPLYING(REPLACE_AND_OF_NOT_AND_NEQ) {
+                        DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()};
+                        orp->lhsp(lhsNotp->srcp());
+                        DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()};
+                        newRhsp->lhsp(rhsNeqp->lhsp());
+                        newRhsp->rhsp(rhsNeqp->rhsp());
+                        orp->rhsp(newRhsp);
+                        DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()};
+                        notp->srcp(orp);
+                        vtxp->replaceWith(notp);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (DfgConst* const lhsConstp = lhsp->cast()) {
+            if (lhsConstp->isZero()) {
+                APPLYING(REPLACE_AND_WITH_ZERO) {
+                    vtxp->replaceWith(lhsConstp);
+                    return;
+                }
+            }
+
+            if (lhsConstp->isOnes()) {
+                APPLYING(REMOVE_AND_WITH_ONES) {
+                    vtxp->replaceWith(rhsp);
+                    return;
+                }
+            }
+
+            if (DfgConcat* const rhsConcatp = rhsp->cast()) {
+                if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return;
+            }
+        }
+
+        if (tryPushBitwiseOpThroughReductions(vtxp)) return;
+
+        if (DfgNot* const lhsNotp = lhsp->cast()) {
+            // ~A & A is all zeroes
+            if (lhsNotp->srcp() == rhsp) {
+                APPLYING(REPLACE_CONTRADICTORY_AND) {
+                    DfgConst* const replacementp = makeZero(flp, vtxp->width());
+                    vtxp->replaceWith(replacementp);
+                    return;
+                }
+            }
+        }
+    }
+
+    void visit(DfgOr* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width");
+
+        if (associativeBinary(vtxp)) return;
+
+        commutativeBinary(vtxp);
+
+        DfgVertex* const lhsp = vtxp->lhsp();
+        DfgVertex* const rhsp = vtxp->rhsp();
+        FileLine* const flp = vtxp->fileline();
+
+        // Bubble pushing
+        if (!vtxp->hasMultipleSinks() && !lhsp->hasMultipleSinks() && !rhsp->hasMultipleSinks()) {
+            if (DfgNot* const lhsNotp = lhsp->cast()) {
+                if (DfgNot* const rhsNotp = rhsp->cast()) {
+                    APPLYING(REPLACE_OR_OF_NOT_AND_NOT) {
+                        DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()};
+                        andp->lhsp(lhsNotp->srcp());
+                        andp->rhsp(rhsNotp->srcp());
+                        DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()};
+                        notp->srcp(andp);
+                        vtxp->replaceWith(notp);
+                        return;
+                    }
+                }
+                if (DfgNeq* const rhsNeqp = rhsp->cast()) {
+                    APPLYING(REPLACE_OR_OF_NOT_AND_NEQ) {
+                        DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()};
+                        andp->lhsp(lhsNotp->srcp());
+                        DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()};
+                        newRhsp->lhsp(rhsNeqp->lhsp());
+                        newRhsp->rhsp(rhsNeqp->rhsp());
+                        andp->rhsp(newRhsp);
+                        DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()};
+                        notp->srcp(andp);
+                        vtxp->replaceWith(notp);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (DfgConcat* const lhsConcatp = lhsp->cast()) {
+            if (DfgConcat* const rhsConcatp = rhsp->cast()) {
+                if (lhsConcatp->lhsp()->dtypep() == rhsConcatp->lhsp()->dtypep()) {
+                    if (lhsConcatp->lhsp()->isZero() && rhsConcatp->rhsp()->isZero()) {
+                        APPLYING(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) {
+                            DfgConcat* const replacementp
+                                = new DfgConcat{m_dfg, flp, vtxp->dtypep()};
+                            replacementp->lhsp(rhsConcatp->lhsp());
+                            replacementp->rhsp(lhsConcatp->rhsp());
+                            vtxp->replaceWith(replacementp);
+                            return;
+                        }
+                    }
+                    if (lhsConcatp->rhsp()->isZero() && rhsConcatp->lhsp()->isZero()) {
+                        APPLYING(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) {
+                            DfgConcat* const replacementp
+                                = new DfgConcat{m_dfg, flp, vtxp->dtypep()};
+                            replacementp->lhsp(lhsConcatp->lhsp());
+                            replacementp->rhsp(rhsConcatp->rhsp());
+                            vtxp->replaceWith(replacementp);
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (DfgConst* const lhsConstp = lhsp->cast()) {
+            if (lhsConstp->isZero()) {
+                APPLYING(REMOVE_OR_WITH_ZERO) {
+                    vtxp->replaceWith(rhsp);
+                    return;
+                }
+            }
+
+            if (lhsConstp->isOnes()) {
+                APPLYING(REPLACE_OR_WITH_ONES) {
+                    vtxp->replaceWith(lhsp);
+                    return;
+                }
+            }
+
+            if (DfgConcat* const rhsConcatp = rhsp->cast()) {
+                if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return;
+            }
+        }
+
+        if (tryPushBitwiseOpThroughReductions(vtxp)) return;
+
+        if (DfgNot* const lhsNotp = lhsp->cast()) {
+            // ~A | A is all ones
+            if (lhsNotp->srcp() == rhsp) {
+                APPLYING(REPLACE_TAUTOLOGICAL_OR) {
+                    DfgConst* const replacementp = makeZero(flp, vtxp->width());
+                    replacementp->num().setAllBits1();
+                    vtxp->replaceWith(replacementp);
+                    return;
+                }
+            }
+        }
+    }
+
+    void visit(DfgXor* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width");
+
+        if (associativeBinary(vtxp)) return;
+
+        commutativeBinary(vtxp);
+
+        DfgVertex* const lhsp = vtxp->lhsp();
+        DfgVertex* const rhsp = vtxp->rhsp();
+        FileLine* const flp = vtxp->fileline();
+
+        if (DfgConst* const lConstp = lhsp->cast()) {
+            if (lConstp->isZero()) {
+                APPLYING(REMOVE_XOR_WITH_ZERO) {
+                    vtxp->replaceWith(rhsp);
+                    return;
+                }
+            }
+            if (lConstp->isOnes()) {
+                APPLYING(REPLACE_XOR_WITH_ONES) {
+                    DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()};
+                    replacementp->srcp(rhsp);
+                    vtxp->replaceWith(replacementp);
+                    return;
+                }
+            }
+            if (DfgConcat* const rConcatp = rhsp->cast()) {
+                tryPushBitwiseOpThroughConcat(vtxp, lConstp, rConcatp);
+                return;
+            }
+        }
+
+        if (tryPushBitwiseOpThroughReductions(vtxp)) return;
+    }
+
+    //=========================================================================
+    //  DfgVertexBinary - other
+    //=========================================================================
+
+    void visit(DfgAdd* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width");
+
+        if (associativeBinary(vtxp)) return;
+
+        commutativeBinary(vtxp);
+    }
+
+    void visit(DfgArraySel* vtxp) override {
+        if (DfgConst* const idxp = vtxp->bitp()->cast()) {
+            if (DfgVarArray* const varp = vtxp->fromp()->cast()) {
+                const size_t idx = idxp->toSizeT();
+                if (DfgVertex* const driverp = varp->driverAt(idx)) {
+                    APPLYING(INLINE_ARRAYSEL) {
+                        vtxp->replaceWith(driverp);
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    void visit(DfgConcat* vtxp) override {
+        UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width() + vtxp->rhsp()->width(), vtxp,
+                    "Inconsistent Concat");
+
+        if (associativeBinary(vtxp)) return;
+
+        DfgVertex* const lhsp = vtxp->lhsp();
+        DfgVertex* const rhsp = vtxp->rhsp();
+
+        FileLine* const flp = vtxp->fileline();
+
+        if (lhsp->isZero()) {
+            DfgConst* const lConstp = lhsp->as();
+            if (DfgSel* const rSelp = rhsp->cast()) {
+                if (vtxp->dtypep() == rSelp->fromp()->dtypep()
+                    && rSelp->lsb() == lConstp->width()) {
+                    APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) {
+                        DfgShiftR* const replacementp = new DfgShiftR{m_dfg, flp, vtxp->dtypep()};
+                        replacementp->lhsp(rSelp->fromp());
+                        replacementp->rhsp(makeI32(flp, lConstp->width()));
+                        vtxp->replaceWith(replacementp);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (rhsp->isZero()) {
+            DfgConst* const rConstp = rhsp->as();
+            if (DfgSel* const lSelp = lhsp->cast()) {
+                if (vtxp->dtypep() == lSelp->fromp()->dtypep() && lSelp->lsb() == 0) {
+                    APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) {
+                        DfgShiftL* const replacementp = new DfgShiftL{m_dfg, flp, vtxp->dtypep()};
+                        replacementp->lhsp(lSelp->fromp());
+                        replacementp->rhsp(makeI32(flp, rConstp->width()));
+                        vtxp->replaceWith(replacementp);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (DfgNot* const lNot = lhsp->cast()) {
+            if (DfgNot* const rNot = rhsp->cast()) {
+                if (!lNot->hasMultipleSinks() && !rNot->hasMultipleSinks()) {
+                    APPLYING(PUSH_CONCAT_THROUGH_NOTS) {
+                        vtxp->lhsp(lNot->srcp());
+                        vtxp->rhsp(rNot->srcp());
+                        DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()};
+                        vtxp->replaceWith(replacementp);
+                        replacementp->srcp(vtxp);
+                        return;
+                    }
+                }
+            }
+        }
+
+        {
+            const auto joinSels = [this](DfgSel* lSelp, DfgSel* rSelp, FileLine* flp) -> DfgSel* {
+                if (lSelp->fromp()->equals(*rSelp->fromp())) {
+                    if (lSelp->lsb() == rSelp->lsb() + rSelp->width()) {
+                        // Two consecutive Sels, make a single Sel.
+                        const uint32_t width = lSelp->width() + rSelp->width();
+                        DfgSel* const joinedSelp = new DfgSel{m_dfg, flp, dtypeForWidth(width)};
+                        joinedSelp->fromp(rSelp->fromp());
+                        joinedSelp->lsb(rSelp->lsb());
+                        return joinedSelp;
+                    }
+                }
+                return nullptr;
+            };
+
+            DfgSel* const lSelp = lhsp->cast();
+            DfgSel* const rSelp = rhsp->cast();
+            if (lSelp && rSelp) {
+                if (DfgSel* const jointSelp = joinSels(lSelp, rSelp, flp)) {
+                    APPLYING(REMOVE_CONCAT_OF_ADJOINING_SELS) {
+                        vtxp->replaceWith(jointSelp);
+                        return;
+                    }
+                }
+            }
+            if (lSelp) {
+                if (DfgConcat* const rConcatp = rhsp->cast()) {
+                    if (DfgSel* const rlSelp = rConcatp->lhsp()->cast()) {
+                        if (DfgSel* const jointSelp = joinSels(lSelp, rlSelp, flp)) {
+                            APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) {
+                                DfgConcat* const replacementp
+                                    = new DfgConcat{m_dfg, flp, vtxp->dtypep()};
+                                replacementp->lhsp(jointSelp);
+                                replacementp->rhsp(rConcatp->rhsp());
+                                vtxp->replaceWith(replacementp);
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+            if (rSelp) {
+                if (DfgConcat* const lConcatp = lhsp->cast()) {
+                    if (DfgSel* const lrlSelp = lConcatp->rhsp()->cast()) {
+                        if (DfgSel* const jointSelp = joinSels(lrlSelp, rSelp, flp)) {
+                            APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) {
+                                DfgConcat* const replacementp
+                                    = new DfgConcat{m_dfg, flp, vtxp->dtypep()};
+                                replacementp->lhsp(lConcatp->lhsp());
+                                replacementp->rhsp(jointSelp);
+                                vtxp->replaceWith(replacementp);
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    void visit(DfgDiv* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgDivS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgEq* vtxp) override {
+        if (foldBinary(vtxp)) return;
+
+        commutativeBinary(vtxp);
+
+        DfgVertex* const lhsp = vtxp->lhsp();
+        DfgVertex* const rhsp = vtxp->rhsp();
+
+        if (DfgConst* const lhsConstp = lhsp->cast()) {
+            if (DfgConcat* const rhsConcatp = rhsp->cast()) {
+                if (tryPushCompareOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return;
+            }
+        }
+    }
+
+    void visit(DfgGt* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgGtS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgGte* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgGteS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLogAnd* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLogEq* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLogIf* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLogOr* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLt* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLtS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLte* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgLteS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgModDiv* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgModDivS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgMul* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width");
+
+        if (associativeBinary(vtxp)) return;
+
+        commutativeBinary(vtxp);
+    }
+
+    void visit(DfgMulS* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width");
+
+        if (associativeBinary(vtxp)) return;
+
+        commutativeBinary(vtxp);
+    }
+
+    void visit(DfgNeq* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgPow* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgPowSS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgPowSU* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgPowUS* vtxp) override {
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgReplicate* vtxp) override {
+        if (vtxp->dtypep() == vtxp->srcp()->dtypep()) {
+            APPLYING(REMOVE_REPLICATE_ONCE) {
+                vtxp->replaceWith(vtxp->srcp());
+                return;
+            }
+        }
+
+        if (foldBinary(vtxp)) return;
+    }
+
+    void visit(DfgShiftL* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched width");
+
+        if (foldBinary(vtxp)) return;
+
+        optimizeShiftRHS(vtxp);
+    }
+
+    void visit(DfgShiftR* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched width");
+
+        if (foldBinary(vtxp)) return;
+
+        optimizeShiftRHS(vtxp);
+    }
+
+    void visit(DfgShiftRS* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched width");
+
+        if (foldBinary(vtxp)) return;
+
+        optimizeShiftRHS(vtxp);
+    }
+
+    void visit(DfgSub* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width");
+
+        if (foldBinary(vtxp)) return;
+
+        DfgVertex* const lhsp = vtxp->lhsp();
+        DfgVertex* const rhsp = vtxp->rhsp();
+
+        if (DfgConst* const rConstp = rhsp->cast()) {
+            if (rConstp->isZero()) {
+                APPLYING(REMOVE_SUB_ZERO) {
+                    vtxp->replaceWith(lhsp);
+                    return;
+                }
+            }
+            if (vtxp->dtypep() == m_bitDType && rConstp->hasValue(1)) {
+                APPLYING(REPLACE_SUB_WITH_NOT) {
+                    DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType};
+                    replacementp->srcp(lhsp);
+                    vtxp->replaceWith(replacementp);
+                    return;
+                }
+            }
+        }
+    }
+
+    //=========================================================================
+    //  DfgVertexTernary
+    //=========================================================================
+
+    void visit(DfgCond* vtxp) override {
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->thenp()->dtypep(), vtxp, "Width mismatch");
+        UASSERT_OBJ(vtxp->dtypep() == vtxp->elsep()->dtypep(), vtxp, "Width mismatch");
+
+        DfgVertex* const condp = vtxp->condp();
+        DfgVertex* const thenp = vtxp->thenp();
+        DfgVertex* const elsep = vtxp->elsep();
+        FileLine* const flp = vtxp->fileline();
+
+        if (condp->dtypep() != m_bitDType) return;
+
+        if (condp->isOnes()) {
+            APPLYING(REMOVE_COND_WITH_TRUE_CONDITION) {
+                vtxp->replaceWith(thenp);
+                return;
+            }
+        }
+
+        if (condp->isZero()) {
+            APPLYING(REMOVE_COND_WITH_FALSE_CONDITION) {
+                vtxp->replaceWith(elsep);
+                return;
+            }
+        }
+
+        if (DfgNot* const condNotp = condp->cast()) {
+            if (!condp->hasMultipleSinks() || condNotp->hasMultipleSinks()) {
+                APPLYING(SWAP_COND_WITH_NOT_CONDITION) {
+                    vtxp->condp(condNotp->srcp());
+                    vtxp->thenp(elsep);
+                    vtxp->elsep(thenp);
+                    return;
+                }
+            }
+        }
+
+        if (DfgNeq* const condNeqp = condp->cast()) {
+            if (!condp->hasMultipleSinks()) {
+                APPLYING(SWAP_COND_WITH_NEQ_CONDITION) {
+                    DfgEq* const newCondp = new DfgEq{m_dfg, condp->fileline(), condp->dtypep()};
+                    newCondp->lhsp(condNeqp->lhsp());
+                    newCondp->rhsp(condNeqp->rhsp());
+                    vtxp->condp(newCondp);
+                    vtxp->thenp(elsep);
+                    vtxp->elsep(thenp);
+                    return;
+                }
+            }
+        }
+
+        if (DfgNot* const thenNotp = thenp->cast()) {
+            if (DfgNot* const elseNotp = elsep->cast()) {
+                if ((!thenp->hasMultipleSinks() || thenNotp->hasMultipleSinks())
+                    && (!elsep->hasMultipleSinks() || elsep->hasMultipleSinks())) {
+                    APPLYING(PULL_NOTS_THROUGH_COND) {
+                        DfgNot* const replacementp
+                            = new DfgNot{m_dfg, thenp->fileline(), vtxp->dtypep()};
+                        vtxp->thenp(thenNotp->srcp());
+                        vtxp->elsep(elseNotp->srcp());
+                        vtxp->replaceWith(replacementp);
+                        replacementp->srcp(vtxp);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (vtxp->width() > 1) {
+            // 'cond ? a + 1 : a' -> 'a + cond'
+            if (DfgAdd* const thenAddp = thenp->cast()) {
+                if (DfgConst* const constp = thenAddp->lhsp()->cast()) {
+                    if (constp->hasValue(1)) {
+                        if (thenAddp->rhsp() == elsep) {
+                            APPLYING(REPLACE_COND_INC) {
+                                DfgConcat* const extp = new DfgConcat{m_dfg, flp, vtxp->dtypep()};
+                                extp->rhsp(condp);
+                                extp->lhsp(makeZero(flp, vtxp->width() - 1));
+                                FileLine* const thenFlp = thenAddp->fileline();
+                                DfgAdd* const addp = new DfgAdd{m_dfg, thenFlp, vtxp->dtypep()};
+                                addp->lhsp(thenAddp->rhsp());
+                                addp->rhsp(extp);
+                                vtxp->replaceWith(addp);
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+            // 'cond ? a - 1 : a' -> 'a - cond'
+            if (DfgSub* const thenSubp = thenp->cast()) {
+                if (DfgConst* const constp = thenSubp->rhsp()->cast()) {
+                    if (constp->hasValue(1)) {
+                        if (thenSubp->lhsp() == elsep) {
+                            APPLYING(REPLACE_COND_DEC) {
+                                DfgConcat* const extp = new DfgConcat{m_dfg, flp, vtxp->dtypep()};
+                                extp->rhsp(condp);
+                                extp->lhsp(makeZero(flp, vtxp->width() - 1));
+                                FileLine* const thenFlp = thenSubp->fileline();
+                                DfgSub* const subp = new DfgSub{m_dfg, thenFlp, vtxp->dtypep()};
+                                subp->lhsp(thenSubp->lhsp());
+                                subp->rhsp(extp);
+                                vtxp->replaceWith(subp);
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (vtxp->dtypep() == m_bitDType) {
+            AstNodeDType* const dtypep = vtxp->dtypep();
+            if (thenp->isZero()) {  // a ? 0 : b becomes ~a & b
+                APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) {
+                    DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep};
+                    DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep};
+                    notp->srcp(condp);
+                    repalcementp->lhsp(notp);
+                    repalcementp->rhsp(elsep);
+                    vtxp->replaceWith(repalcementp);
+                    return;
+                }
+            }
+            if (thenp->isOnes()) {  // a ? 1 : b becomes a | b
+                APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) {
+                    DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep};
+                    repalcementp->lhsp(condp);
+                    repalcementp->rhsp(elsep);
+                    vtxp->replaceWith(repalcementp);
+                    return;
+                }
+            }
+            if (elsep->isZero()) {  // a ? b : 0 becomes a & b
+                APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) {
+                    DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep};
+                    repalcementp->lhsp(condp);
+                    repalcementp->rhsp(thenp);
+                    vtxp->replaceWith(repalcementp);
+                    return;
+                }
+            }
+            if (elsep->isOnes()) {  // a ? b : 1 becomes ~a | b
+                APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) {
+                    DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep};
+                    DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep};
+                    notp->srcp(condp);
+                    repalcementp->lhsp(notp);
+                    repalcementp->rhsp(thenp);
+                    vtxp->replaceWith(repalcementp);
+                    return;
+                }
+            }
+        }
+    }
+
+#undef APPLYING
+
+    // Process one vertex. Return true if graph changed
+    void processVertex(DfgVertex* vtxp) {
+        // If it has no sinks (unused), we can remove it
+        if (!vtxp->hasSinks()) {
+            vtxp->unlinkDelete(m_dfg);
+            m_changed = true;
+            return;
+        }
+
+        // Transform node
+        iterate(vtxp);
+
+        // If it became unused, we can remove it
+        if (!vtxp->hasSinks()) {
+            UASSERT_OBJ(m_changed, vtxp, "'m_changed' must be set if node became unused");
+            vtxp->unlinkDelete(m_dfg);
+        }
+    }
+
+    V3DfgPeephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx)
+        : m_dfg{dfg}
+        , m_ctx{ctx} {
+
+        while (true) {
+            // Do one pass over the graph in the forward direction.
+            m_changed = false;
+            for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+                nextp = vtxp->verticesNext();
+                if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp);
+                processVertex(vtxp);
+            }
+            if (!m_changed) break;
+
+            // Do another pass in the opposite direction. Alternating directions reduces
+            // the pathological complexity with left/right leaning trees.
+            m_changed = false;
+            for (DfgVertex *vtxp = m_dfg.opVerticesRbeginp(), *nextp; vtxp; vtxp = nextp) {
+                nextp = vtxp->verticesPrev();
+                if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp);
+                processVertex(vtxp);
+            }
+            if (!m_changed) break;
+        }
+    }
+
+public:
+    static void apply(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { V3DfgPeephole{dfg, ctx}; }
+};
+
+void V3DfgPasses::peephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) {
+    V3DfgPeephole::apply(dfg, ctx);
+}
diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h
new file mode 100644
index 000000000..6323fbe13
--- /dev/null
+++ b/src/V3DfgPeephole.h
@@ -0,0 +1,134 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Peephole optimizations over DfgGraph
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3DFGPEEPHOLE_H_
+#define VERILATOR_V3DFGPEEPHOLE_H_
+
+#include "config_build.h"
+
+#include 
+
+#define _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, arg) macro(arg, #arg)
+
+// Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests).
+// clang-format off
+#define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro)   \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_NOTS) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_NOT_THROUGH_COND) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_CONCAT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_CONCAT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_COND) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_ONES) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_CONCAT_OF_ADJOINING_SELS) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_FALSE_CONDITION) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_TRUE_CONDITION) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_FULL_WIDTH_SEL) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_NOT_NOT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_OR_WITH_ZERO) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REPLICATE_ONCE) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_LHS_OF_CONCAT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_RHS_OF_CONCAT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SUB_ZERO) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_WIDTH_ONE_REDUCTION) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_XOR_WITH_ZERO) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NEQ) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NOT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_WITH_ZERO) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ONES) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ZERO) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ONES) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ZERO) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONTRADICTORY_AND) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EXTEND) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_EQ) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_NEQ) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NEQ) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NOT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_WITH_ONES) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_SEL) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SUB_WITH_NOT) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_TAUTOLOGICAL_OR) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_XOR_WITH_ONES) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_ASSOC) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_CONST_IN_COMMUTATIVE_BINARY) \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_NOT_IN_COMMUTATIVE_BINARY)   \
+    _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_VAR_IN_COMMUTATIVE_BINARY)
+// clang-format on
+
+class VDfgPeepholePattern final {
+public:
+    enum en : unsigned {
+#define OPTIMIZATION_ID(id, name) id,
+        FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_ID)
+#undef OPTIMIZATION_ID
+            _ENUM_END
+    };
+    enum en m_e;
+    const char* ascii() const {
+        static const char* const names[] = {
+#define OPTIMIZATION_NAME(id, name) name,
+            FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_NAME)
+#undef OPTIMIZATION_NAME
+                "_ENUM_END"  //
+        };
+        return names[m_e];
+    }
+
+    // cppcheck-suppress noExplicitConstructor
+    VDfgPeepholePattern(en _e)
+        : m_e{_e} {}
+    operator en() const { return m_e; }
+};
+
+struct V3DfgPeepholeContext final {
+    const std::string m_label;  // Label to apply to stats
+
+    // Enable flags for each optimization
+    bool m_enabled[VDfgPeepholePattern::_ENUM_END];
+    // Count of applications for each optimization (for statistics)
+    VDouble0 m_count[VDfgPeepholePattern::_ENUM_END];
+
+    explicit V3DfgPeepholeContext(const std::string& label);
+    ~V3DfgPeepholeContext();
+};
+
+#endif
diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h
new file mode 100644
index 000000000..5c3700308
--- /dev/null
+++ b/src/V3DfgVertices.h
@@ -0,0 +1,286 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: DfgVertex sub-classes
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// This is a data-flow graph based representation of combinational logic,
+// the main difference from a V3Graph is that DfgVertex owns the storage
+// of it's input edges (operands/sources/arguments), and can access each
+// input edge directly by indexing, making modifications more efficient
+// than the linked list based structures used by V3Graph.
+//
+// A bulk of the DfgVertex sub-types are generated by astgen, and are
+// analogous to the corresponding AstNode sub-types.
+//
+// See also the internals documentation docs/internals.rst
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3DFGVERTICES_H_
+#define VERILATOR_V3DFGVERTICES_H_
+
+#ifndef VERILATOR_V3DFG_H_
+#error "Use V3Dfg.h as the include"
+#include "V3Dfg.h"  // This helps code analysis tools pick up symbols in V3Dfg.h
+#define VL_NOT_FINAL  // This #define fixes broken code folding in the CLion IDE
+#endif
+
+// === Abstract base node types (DfgVertex*) ===================================
+
+class DfgVertexVar VL_NOT_FINAL : public DfgVertexVariadic {
+    AstVar* const m_varp;  // The AstVar associated with this vertex (not owned by this vertex)
+    bool m_hasModRefs = false;  // This AstVar is referenced outside the DFG, but in the module
+    bool m_hasExtRefs = false;  // This AstVar is referenced from outside the module
+
+    bool selfEquals(const DfgVertex& that) const final;
+    V3Hash selfHash() const final;
+
+public:
+    DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity)
+        : DfgVertexVariadic{dfg, type, varp->fileline(), dtypeFor(varp), initialCapacity}
+        , m_varp{varp} {}
+    ASTGEN_MEMBERS_DfgVertexVar;
+
+    DfgVertexVar* verticesNext() const {
+        return static_cast(DfgVertex::verticesNext());
+    }
+    DfgVertexVar* verticesPrev() const {
+        return static_cast(DfgVertex::verticesPrev());
+    }
+
+    bool isDrivenByDfg() const { return arity() > 0; }
+
+    AstVar* varp() const { return m_varp; }
+    bool hasModRefs() const { return m_hasModRefs; }
+    void setHasModRefs() { m_hasModRefs = true; }
+    bool hasExtRefs() const { return m_hasExtRefs; }
+    void setHasExtRefs() { m_hasExtRefs = true; }
+    bool hasRefs() const { return m_hasModRefs || m_hasExtRefs; }
+
+    // Variable cannot be removed, even if redundant in the DfgGraph (might be used externally)
+    bool keep() const {
+        // Keep if referenced outside this module
+        if (hasExtRefs()) return true;
+        // Keep if traced
+        if (v3Global.opt.trace() && varp()->isTrace()) return true;
+        // Keep if public
+        if (varp()->isSigPublic()) return true;
+        // Otherwise it can be removed
+        return false;
+    }
+};
+
+// === Concrete node types =====================================================
+
+// === DfgVertex ===
+class DfgConst final : public DfgVertex {
+    friend class DfgVertex;
+    friend class DfgVisitor;
+
+    V3Number m_num;  // Constant value
+
+    bool selfEquals(const DfgVertex& that) const override;
+    V3Hash selfHash() const override;
+
+public:
+    DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num)
+        : DfgVertex{dfg, dfgType(), flp, dtypeForWidth(num.width())}
+        , m_num{num} {}
+    DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value = 0)
+        : DfgVertex{dfg, dfgType(), flp, dtypeForWidth(width)}
+        , m_num{flp, static_cast(width), value} {}
+    ASTGEN_MEMBERS_DfgConst;
+
+    DfgConst* verticesNext() const { return static_cast(DfgVertex::verticesNext()); }
+    DfgConst* verticesPrev() const { return static_cast(DfgVertex::verticesPrev()); }
+
+    V3Number& num() { return m_num; }
+    const V3Number& num() const { return m_num; }
+
+    size_t toSizeT() const {
+        if VL_CONSTEXPR_CXX17 (sizeof(size_t) > sizeof(uint32_t)) {
+            return static_cast(num().toUQuad());
+        }
+        return static_cast(num().toUInt());
+    }
+
+    bool isZero() const { return num().isEqZero(); }
+    bool isOnes() const { return num().isEqAllOnes(width()); }
+
+    // Does this DfgConst have the given value? Note this is not easy to answer if wider than 32.
+    bool hasValue(uint32_t value) const {
+        return !num().isFourState() && num().edataWord(0) == value && num().mostSetBitP1() <= 32;
+    }
+
+    std::pair sourceEdges() override { return {nullptr, 0}; }
+    std::pair sourceEdges() const override { return {nullptr, 0}; }
+    const string srcName(size_t) const override {  // LCOV_EXCL_START
+        VL_UNREACHABLE;
+        return "";
+    }  // LCOV_EXCL_STOP
+};
+
+// === DfgVertexBinary ===
+class DfgMux final : public DfgVertexBinary {
+    // AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and
+    // 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for
+    // the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'.
+public:
+    DfgMux(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
+        : DfgVertexBinary{dfg, dfgType(), flp, dtypep} {}
+    ASTGEN_MEMBERS_DfgMux;
+
+    DfgVertex* fromp() const { return source<0>(); }
+    void fromp(DfgVertex* vtxp) { relinkSource<0>(vtxp); }
+    DfgVertex* lsbp() const { return source<1>(); }
+    void lsbp(DfgVertex* vtxp) { relinkSource<1>(vtxp); }
+
+    const string srcName(size_t idx) const override { return idx ? "lsbp" : "fromp"; }
+};
+
+// === DfgVertexUnary ===
+class DfgSel final : public DfgVertexUnary {
+    // AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and
+    // 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for
+    // the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'.
+    uint32_t m_lsb = 0;  // The LSB index
+
+    bool selfEquals(const DfgVertex& that) const override;
+    V3Hash selfHash() const override;
+
+public:
+    DfgSel(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
+        : DfgVertexUnary{dfg, dfgType(), flp, dtypep} {}
+    ASTGEN_MEMBERS_DfgSel;
+
+    DfgVertex* fromp() const { return source<0>(); }
+    void fromp(DfgVertex* vtxp) { relinkSource<0>(vtxp); }
+    uint32_t lsb() const { return m_lsb; }
+    void lsb(uint32_t value) { m_lsb = value; }
+
+    const string srcName(size_t) const override { return "fromp"; }
+};
+
+// === DfgVertexVar ===
+class DfgVarArray final : public DfgVertexVar {
+    friend class DfgVertex;
+    friend class DfgVisitor;
+
+    using DriverData = std::pair;
+
+    std::vector m_driverData;  // Additional data associate with each driver
+
+public:
+    DfgVarArray(DfgGraph& dfg, AstVar* varp)
+        : DfgVertexVar{dfg, dfgType(), varp, 4u} {
+        UASSERT_OBJ(VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType), varp, "Non array DfgVarArray");
+    }
+    ASTGEN_MEMBERS_DfgVarArray;
+
+    void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) {
+        m_driverData.emplace_back(flp, index);
+        DfgVertexVariadic::addSource()->relinkSource(vtxp);
+    }
+
+    void resetSources() {
+        m_driverData.clear();
+        DfgVertexVariadic::resetSources();
+    }
+
+    // Remove undriven sources
+    void packSources() {
+        // Grab and reset the driver data
+        std::vector driverData{std::move(m_driverData)};
+
+        // Grab and unlink the sources
+        std::vector sources{arity()};
+        forEachSourceEdge([&](DfgEdge& edge, size_t idx) {
+            sources[idx] = edge.sourcep();
+            edge.unlinkSource();
+        });
+        DfgVertexVariadic::resetSources();
+
+        // Add back the driven sources
+        for (size_t i = 0; i < sources.size(); ++i) {
+            if (!sources[i]) continue;
+            addDriver(driverData[i].first, driverData[i].second, sources[i]);
+        }
+    }
+
+    FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; }
+    uint32_t driverIndex(size_t idx) const { return m_driverData[idx].second; }
+
+    DfgVertex* driverAt(size_t idx) const {
+        const DfgEdge* const edgep = findSourceEdge([=](const DfgEdge&, size_t i) {  //
+            return driverIndex(i) == idx;
+        });
+        return edgep ? edgep->sourcep() : nullptr;
+    }
+
+    const string srcName(size_t idx) const override { return cvtToStr(driverIndex(idx)); }
+};
+class DfgVarPacked final : public DfgVertexVar {
+    friend class DfgVertex;
+    friend class DfgVisitor;
+
+    using DriverData = std::pair;
+
+    std::vector m_driverData;  // Additional data associate with each driver
+
+public:
+    DfgVarPacked(DfgGraph& dfg, AstVar* varp)
+        : DfgVertexVar{dfg, dfgType(), varp, 1u} {}
+    ASTGEN_MEMBERS_DfgVarPacked;
+
+    bool isDrivenFullyByDfg() const { return arity() == 1 && source(0)->dtypep() == dtypep(); }
+
+    void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) {
+        m_driverData.emplace_back(flp, lsb);
+        DfgVertexVariadic::addSource()->relinkSource(vtxp);
+    }
+
+    void resetSources() {
+        m_driverData.clear();
+        DfgVertexVariadic::resetSources();
+    }
+
+    // Remove undriven sources
+    void packSources() {
+        // Grab and reset the driver data
+        std::vector driverData{std::move(m_driverData)};
+
+        // Grab and unlink the sources
+        std::vector sources{arity()};
+        forEachSourceEdge([&](DfgEdge& edge, size_t idx) {
+            sources[idx] = edge.sourcep();
+            edge.unlinkSource();
+        });
+        DfgVertexVariadic::resetSources();
+
+        // Add back the driven sources
+        for (size_t i = 0; i < sources.size(); ++i) {
+            if (!sources[i]) continue;
+            addDriver(driverData[i].first, driverData[i].second, sources[i]);
+        }
+    }
+
+    FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; }
+    uint32_t driverLsb(size_t idx) const { return m_driverData[idx].second; }
+
+    const string srcName(size_t idx) const override {
+        return isDrivenFullyByDfg() ? "" : cvtToStr(driverLsb(idx));
+    }
+};
+
+#endif
diff --git a/src/V3EmitCBase.h b/src/V3EmitCBase.h
index ca293eef1..8f7a97dd8 100644
--- a/src/V3EmitCBase.h
+++ b/src/V3EmitCBase.h
@@ -54,7 +54,7 @@ public:
     V3OutCFile* m_ofp = nullptr;
     bool m_trackText = false;  // Always track AstText nodes
     // METHODS
-    V3OutCFile* ofp() const { return m_ofp; }
+    V3OutCFile* ofp() const VL_MT_SAFE { return m_ofp; }
     void puts(const string& str) { ofp()->puts(str); }
     void putbs(const string& str) { ofp()->putbs(str); }
     void putsDecoration(const string& str) {
diff --git a/src/V3EmitCConstInit.h b/src/V3EmitCConstInit.h
index fbcac4c16..9b4c2b4e9 100644
--- a/src/V3EmitCConstInit.h
+++ b/src/V3EmitCConstInit.h
@@ -101,7 +101,9 @@ protected:
         const V3Number& num = nodep->num();
         UASSERT_OBJ(!num.isFourState(), nodep, "4-state value in constant pool");
         const AstNodeDType* const dtypep = nodep->dtypep();
-        if (num.isString()) {
+        if (num.isNull()) {
+            puts("VlNull{}");
+        } else if (num.isString()) {
             // Note: putsQuoted does not track indentation, so we use this instead
             puts("\"");
             puts(num.toString());
diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp
index 3bda90bb6..f81998976 100644
--- a/src/V3EmitCFunc.cpp
+++ b/src/V3EmitCFunc.cpp
@@ -483,7 +483,9 @@ void EmitCFunc::emitCvtWideArray(AstNode* nodep, AstNode* fromp) {
 
 void EmitCFunc::emitConstant(AstConst* nodep, AstVarRef* assigntop, const string& assignString) {
     // Put out constant set to the specified variable, or given variable in a string
-    if (nodep->num().isFourState()) {
+    if (nodep->num().isNull()) {
+        puts("VlNull{}");
+    } else if (nodep->num().isFourState()) {
         nodep->v3warn(E_UNSUPPORTED, "Unsupported: 4-state numbers in this context");
     } else if (nodep->num().isString()) {
         putbs("std::string{");
@@ -625,7 +627,7 @@ void EmitCFunc::emitVarReset(AstVar* varp) {
             }
         } else if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
             if (initarp->defaultp()) {
-                puts("for (int __Vi=0; __Vi<" + cvtToStr(adtypep->elementsConst()));
+                puts("for (int __Vi = 0; __Vi < " + cvtToStr(adtypep->elementsConst()));
                 puts("; ++__Vi) {\n");
                 emitSetVarConstant(varNameProtected + "[__Vi]", VN_AS(initarp->defaultp(), Const));
                 puts("}\n");
@@ -661,6 +663,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
                                    suffix + ".atDefault()" + cvtarray);
     } else if (VN_IS(dtypep, ClassRefDType)) {
         return "";  // Constructor does it
+    } else if (VN_IS(dtypep, IfaceRefDType)) {
+        return varNameProtected + suffix + " = nullptr;\n";
     } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) {
         // Access std::array as C array
         const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
@@ -675,7 +679,7 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
         UASSERT_OBJ(adtypep->hi() >= adtypep->lo(), varp,
                     "Should have swapped msb & lsb earlier.");
         const string ivar = string("__Vi") + cvtToStr(depth);
-        const string pre = ("for (int " + ivar + "=" + cvtToStr(0) + "; " + ivar + "<"
+        const string pre = ("for (int " + ivar + " = " + cvtToStr(0) + "; " + ivar + " < "
                             + cvtToStr(adtypep->elementsConst()) + "; ++" + ivar + ") {\n");
         const string below = emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(),
                                                  depth + 1, suffix + "[" + ivar + "]");
@@ -684,6 +688,14 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
     } else if (basicp && basicp->keyword() == VBasicDTypeKwd::STRING) {
         // String's constructor deals with it
         return "";
+    } else if (basicp && basicp->isForkSync()) {
+        return "";
+    } else if (basicp && basicp->isDelayScheduler()) {
+        return "";
+    } else if (basicp && basicp->isTriggerScheduler()) {
+        return "";
+    } else if (basicp && basicp->isDynamicTriggerScheduler()) {
+        return "";
     } else if (basicp) {
         const bool zeroit
             = (varp->attrFileDescr()  // Zero so we don't do file IO if never $fopen
@@ -727,80 +739,3 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
     }
     return "";
 }
-
-void EmitCFunc::doubleOrDetect(AstChangeDet* changep, bool& gotOne) {
-    // cppcheck-suppress variableScope
-    static int s_addDoubleOr = 10;  // Determined experimentally as best
-    if (!changep->rhsp()) {
-        if (!gotOne) {
-            gotOne = true;
-        } else {
-            puts(" | ");
-        }
-        iterateAndNextNull(changep->lhsp());
-    } else {
-        AstNode* const lhsp = changep->lhsp();
-        AstNode* const rhsp = changep->rhsp();
-        UASSERT_OBJ(VN_IS(lhsp, VarRef) || VN_IS(lhsp, ArraySel), changep, "Not ref?");
-        UASSERT_OBJ(VN_IS(rhsp, VarRef) || VN_IS(rhsp, ArraySel), changep, "Not ref?");
-        for (int word = 0; word < (changep->lhsp()->isWide() ? changep->lhsp()->widthWords() : 1);
-             ++word) {
-            if (!gotOne) {
-                gotOne = true;
-                s_addDoubleOr = 10;
-                puts("(");
-            } else if (--s_addDoubleOr == 0) {
-                puts("|| (");
-                s_addDoubleOr = 10;
-            } else {
-                puts(" | (");
-            }
-            iterateAndNextNull(changep->lhsp());
-            if (changep->lhsp()->isWide()) puts("[" + cvtToStr(word) + "]");
-            if (changep->lhsp()->isDouble()) {
-                puts(" != ");
-            } else {
-                puts(" ^ ");
-            }
-            iterateAndNextNull(changep->rhsp());
-            if (changep->lhsp()->isWide()) puts("[" + cvtToStr(word) + "]");
-            puts(")");
-        }
-    }
-}
-
-void EmitCFunc::emitChangeDet() {
-    putsDecoration("// Change detection\n");
-    puts("QData __req = false;  // Logically a bool\n");  // But not because it results in
-    // faster code
-    bool gotOne = false;
-    for (AstChangeDet* const changep : m_blkChangeDetVec) {
-        if (changep->lhsp()) {
-            if (!gotOne) {  // Not a clocked block
-                puts("__req |= (");
-            } else {
-                puts("\n");
-            }
-            doubleOrDetect(changep, gotOne);
-        }
-    }
-    if (gotOne) puts(");\n");
-    if (gotOne && !v3Global.opt.protectIds()) {
-        // puts("VL_DEBUG_IF( if (__req) cout<<\"- CLOCKREQ );");
-        for (AstChangeDet* nodep : m_blkChangeDetVec) {
-            if (nodep->lhsp()) {
-                puts("VL_DEBUG_IF( if(__req && (");
-                bool gotOneIgnore = false;
-                doubleOrDetect(nodep, gotOneIgnore);
-                string varname;
-                if (VN_IS(nodep->lhsp(), VarRef)) {
-                    varname = ": " + VN_AS(nodep->lhsp(), VarRef)->varp()->prettyName();
-                }
-                puts(")) VL_DBG_MSGF(\"        CHANGE: ");
-                puts(protect(nodep->fileline()->filename()));
-                puts(":" + cvtToStr(nodep->fileline()->lineno()));
-                puts(varname + "\\n\"); );\n");
-            }
-        }
-    }
-}
diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h
index 7a9abae9f..c91ef4174 100644
--- a/src/V3EmitCFunc.h
+++ b/src/V3EmitCFunc.h
@@ -119,7 +119,6 @@ private:
     int m_labelNum = 0;  // Next label number
     int m_splitSize = 0;  // # of cfunc nodes placed into output file
     bool m_inUC = false;  // Inside an AstUCStmt or AstUCMath
-    std::vector m_blkChangeDetVec;  // All encountered changes in block
     bool m_emitConstInit = false;  // Emitting constant initializer
 
     // State associated with processing $display style string formatting
@@ -198,7 +197,6 @@ public:
     void emitVarReset(AstVar* varp);
     string emitVarResetRecurse(const AstVar* varp, const string& varNameProtected,
                                AstNodeDType* dtypep, int depth, const string& suffix);
-    void doubleOrDetect(AstChangeDet* changep, bool& gotOne);
     void emitChangeDet();
     void emitConstInit(AstNode* initp) {
         // We should refactor emit to produce output into a provided buffer, not go through members
@@ -215,8 +213,6 @@ public:
         VL_RESTORER(m_cfuncp);
         m_cfuncp = nodep;
 
-        m_blkChangeDetVec.clear();
-
         splitSizeInc(nodep);
 
         puts("\n");
@@ -244,7 +240,7 @@ public:
 
         // "+" in the debug indicates a print from the model
         puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+  ");
-        for (int i = 0; i < m_modp->level(); ++i) { puts("  "); }
+        for (int i = 0; i < m_modp->level(); ++i) puts("  ");
         puts(prefixNameProtect(m_modp));
         puts(nodep->isLoose() ? "__" : "::");
         puts(nodep->nameProtect() + "\\n\"); );\n");
@@ -265,15 +261,11 @@ public:
             iterateAndNextNull(nodep->stmtsp());
         }
 
-        if (!m_blkChangeDetVec.empty()) emitChangeDet();
-
         if (nodep->finalsp()) {
             putsDecoration("// Final\n");
             iterateAndNextNull(nodep->finalsp());
         }
 
-        if (!m_blkChangeDetVec.empty()) puts("return __req;\n");
-
         puts("}\n");
         if (nodep->ifdef() != "") puts("#endif  // " + nodep->ifdef() + "\n");
     }
@@ -420,9 +412,14 @@ public:
         puts(funcp->nameProtect());
         emitCCallArgs(nodep, "");
     }
+    void visit(AstCAwait* nodep) override {
+        puts("co_await ");
+        iterate(nodep->exprp());
+        if (nodep->isStatement()) puts(";\n");
+    }
     void visit(AstCNew* nodep) override {
         bool comma = false;
-        puts("std::make_shared<" + prefixNameProtect(nodep->dtypep()) + ">(");
+        puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", ");
         puts("vlSymsp");  // TODO make this part of argsp, and eliminate when unnecessary
         if (nodep->argsp()) comma = true;
         for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) {
@@ -1057,7 +1054,7 @@ public:
         puts(")");
     }
     void visit(AstNewCopy* nodep) override {
-        puts("std::make_shared<" + prefixNameProtect(nodep->dtypep()) + ">(");
+        puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", ");
         puts("*");  // i.e. make into a reference
         iterateAndNextNull(nodep->rhsp());
         puts(")");
@@ -1148,6 +1145,9 @@ public:
         } else if (VN_IS(varModp, Class) && varModp != m_modp) {
             // Superclass member reference
             puts(prefixNameProtect(varModp) + "::");
+        } else if (varp->isIfaceRef()) {
+            puts(nodep->selfPointerProtect(m_useSelfForThis));
+            return;
         } else if (!nodep->selfPointer().empty()) {
             emitDereference(nodep->selfPointerProtect(m_useSelfForThis));
         }
@@ -1171,6 +1171,12 @@ public:
             emitConstant(nodep, nullptr, "");
         }
     }
+    void visit(AstThisRef* nodep) override {
+        putbs(nodep->dtypep()->cType("", false, false));
+        puts("{");
+        puts(m_useSelfForThis ? "vlSelf" : "this");
+        puts("}");
+    }
 
     //
     void visit(AstMTaskBody* nodep) override {
@@ -1254,9 +1260,6 @@ public:
         UASSERT_OBJ(!nodep->mTaskBodiesp(), nodep, "These should have been lowered");
         iterateChildrenConst(nodep);
     }
-    void visit(AstChangeDet* nodep) override {  //
-        m_blkChangeDetVec.push_back(nodep);
-    }
 
     // Default
     void visit(AstNode* nodep) override {
diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp
index 6e1a541db..d9197cd0c 100644
--- a/src/V3EmitCHeaders.cpp
+++ b/src/V3EmitCHeaders.cpp
@@ -256,9 +256,11 @@ class EmitCHeader final : public EmitCConstInit {
         puts("\nclass ");
         puts(prefixNameProtect(modp));
         if (const AstClass* const classp = VN_CAST(modp, Class)) {
+            puts(" : public ");
             if (classp->extendsp()) {
-                puts(" : public ");
                 puts(prefixNameProtect(classp->extendsp()->classp()));
+            } else {
+                puts("VlClass");
             }
         } else {
             puts(" final : public VerilatedModule");
@@ -314,6 +316,7 @@ 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.usesTiming()) puts("#include \"verilated_timing.h\"\n");
 
         emitAll(modp);
 
diff --git a/src/V3EmitCImp.cpp b/src/V3EmitCImp.cpp
index c01594c13..05749c259 100644
--- a/src/V3EmitCImp.cpp
+++ b/src/V3EmitCImp.cpp
@@ -75,6 +75,7 @@ class EmitCGatherDependencies final : VNVisitor {
         iterateChildrenConst(nodep);
     }
     void visit(AstCNew* nodep) override {
+        addSymsDependency();
         addDTypeDependency(nodep->dtypep());
         iterateChildrenConst(nodep);
     }
@@ -83,6 +84,7 @@ class EmitCGatherDependencies final : VNVisitor {
         iterateChildrenConst(nodep);
     }
     void visit(AstNewCopy* nodep) override {
+        addSymsDependency();
         addDTypeDependency(nodep->dtypep());
         iterateChildrenConst(nodep);
     }
@@ -259,6 +261,10 @@ class EmitCImp final : EmitCFunc {
                         puts("(");
                         putsQuoted(varp->nameProtect());
                         puts(")\n");
+                    } else if (dtypep->isDelayScheduler()) {
+                        puts(", ");
+                        puts(varp->nameProtect());
+                        puts("{*symsp->_vm_contextp__}\n");
                     }
                 }
             }
@@ -377,6 +383,7 @@ class EmitCImp final : EmitCFunc {
                             // lower level subinst code does it.
                         } else if (varp->isParam()) {
                         } else if (varp->isStatic() && varp->isConst()) {
+                        } else if (varp->basicp() && varp->basicp()->isTriggerVec()) {
                         } else {
                             int vects = 0;
                             AstNodeDType* elementp = varp->dtypeSkipRefp();
@@ -386,8 +393,8 @@ class EmitCImp final : EmitCFunc {
                                 UASSERT_OBJ(arrayp->hi() >= arrayp->lo(), varp,
                                             "Should have swapped msb & lsb earlier.");
                                 const string ivar = string("__Vi") + cvtToStr(vecnum);
-                                puts("for (int __Vi" + cvtToStr(vecnum) + "=" + cvtToStr(0));
-                                puts("; " + ivar + "<" + cvtToStr(arrayp->elementsConst()));
+                                puts("for (int __Vi" + cvtToStr(vecnum) + " = " + cvtToStr(0));
+                                puts("; " + ivar + " < " + cvtToStr(arrayp->elementsConst()));
                                 puts("; ++" + ivar + ") {\n");
                                 elementp = arrayp->subDTypep()->skipRefp();
                             }
@@ -400,8 +407,8 @@ class EmitCImp final : EmitCFunc {
                                 && !(basicp && basicp->keyword() == VBasicDTypeKwd::STRING)) {
                                 const int vecnum = vects++;
                                 const string ivar = string("__Vi") + cvtToStr(vecnum);
-                                puts("for (int __Vi" + cvtToStr(vecnum) + "=" + cvtToStr(0));
-                                puts("; " + ivar + "<" + cvtToStr(elementp->widthWords()));
+                                puts("for (int __Vi" + cvtToStr(vecnum) + " = " + cvtToStr(0));
+                                puts("; " + ivar + " < " + cvtToStr(elementp->widthWords()));
                                 puts("; ++" + ivar + ") {\n");
                             }
                             puts("os" + op + varp->nameProtect());
diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp
index a4f95f486..ca6cc31ea 100644
--- a/src/V3EmitCMain.cpp
+++ b/src/V3EmitCMain.cpp
@@ -64,8 +64,8 @@ private:
         puts("int main(int argc, char** argv, char**) {\n");
         puts("// Setup context, defaults, and parse command line\n");
         puts("Verilated::debug(0);\n");
-        if (v3Global.opt.trace()) puts("Verilated::traceEverOn(true);\n");
         puts("const std::unique_ptr contextp{new VerilatedContext};\n");
+        if (v3Global.opt.trace()) puts("contextp->traceEverOn(true);\n");
         puts("contextp->commandArgs(argc, argv);\n");
         puts("\n");
 
@@ -74,16 +74,17 @@ private:
              + "{contextp.get()}};\n");
         puts("\n");
 
-        puts("// Evaluate initials\n");
-        puts("topp->eval();  // Evaluate\n");
-        puts("\n");
-
         puts("// Simulate until $finish\n");
         puts("while (!contextp->gotFinish()) {\n");
         puts(/**/ "// Evaluate model\n");
         puts(/**/ "topp->eval();\n");
         puts(/**/ "// Advance time\n");
-        puts(/**/ "contextp->timeInc(1);\n");
+        if (v3Global.rootp()->delaySchedulerp()) {
+            puts("if (!topp->eventsPending()) break;\n");
+            puts("contextp->time(topp->nextTimeSlot());\n");
+        } else {
+            puts("contextp->timeInc(1);\n");
+        }
 
         puts("}\n");
         puts("\n");
diff --git a/src/V3EmitCMake.cpp b/src/V3EmitCMake.cpp
index d72f6a863..cf7b17563 100644
--- a/src/V3EmitCMake.cpp
+++ b/src/V3EmitCMake.cpp
@@ -114,6 +114,8 @@ class CMakeEmitter final {
         cmake_set_raw(*of, name + "_SC", v3Global.opt.systemC() ? "1" : "0");
         *of << "# Coverage output mode?  0/1 (from --coverage)\n";
         cmake_set_raw(*of, name + "_COVERAGE", v3Global.opt.coverage() ? "1" : "0");
+        *of << "# Timing mode?  0/1\n";
+        cmake_set_raw(*of, name + "_TIMING", v3Global.usesTiming() ? "1" : "0");
         *of << "# Threaded output mode?  0/1/N threads (from --threads)\n";
         cmake_set_raw(*of, name + "_THREADS", cvtToStr(v3Global.opt.threads()));
         *of << "# VCD Tracing output mode?  0/1 (from --trace)\n";
@@ -170,6 +172,9 @@ class CMakeEmitter final {
                                     + ".cpp");
             }
         }
+        if (v3Global.usesTiming()) {
+            global.emplace_back("${VERILATOR_ROOT}/include/verilated_timing.cpp");
+        }
         if (v3Global.opt.threads()) {
             global.emplace_back("${VERILATOR_ROOT}/include/verilated_threads.cpp");
         }
diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp
index 52141a109..517d2618c 100644
--- a/src/V3EmitCModel.cpp
+++ b/src/V3EmitCModel.cpp
@@ -114,6 +114,7 @@ class EmitCModel final : public EmitCFunc {
                 }
             }
         }
+        if (optSystemC() && v3Global.usesTiming()) puts("sc_event trigger_eval;\n");
 
         // Cells instantiated by the top level (for access to /* verilator public */)
         puts("\n// CELLS\n"
@@ -162,7 +163,12 @@ class EmitCModel final : public EmitCFunc {
         if (!optSystemC()) {
             puts("/// Evaluate the model.  Application must call when inputs change.\n");
         }
-        puts("void eval() { eval_step(); " + callEvalEndStep + "}\n");
+        if (optSystemC() && v3Global.usesTiming()) {
+            puts("void eval();\n");
+            puts("void eval_sens();\n");
+        } else {
+            puts("void eval() { eval_step(); " + callEvalEndStep + "}\n");
+        }
         if (!optSystemC()) {
             puts("/// Evaluate when calling multiple units/models per time step.\n");
         }
@@ -184,6 +190,11 @@ class EmitCModel final : public EmitCFunc {
         ofp()->putsPrivate(false);  // public:
         puts("void final();\n");
 
+        puts("/// Are there scheduled events to handle?\n");
+        puts("bool eventsPending();\n");
+        puts("/// Returns time at next time slot. Aborts if !eventsPending()\n");
+        puts("uint64_t nextTimeSlot();\n");
+
         if (v3Global.opt.trace()) {
             puts("/// Trace signals in the model; called by application code\n");
             puts("void trace(" + v3Global.opt.traceClassBase()
@@ -278,6 +289,7 @@ class EmitCModel final : public EmitCFunc {
             // Create sensitivity list for when to evaluate the model.
             putsDecoration("// Sensitivities on all clocks and combinational inputs\n");
             puts("SC_METHOD(eval);\n");
+            if (v3Global.usesTiming()) puts("SC_METHOD(eval_sens);\n");
             for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
                 if (const AstVar* const varp = VN_CAST(nodep, Var)) {
                     if (varp->isNonOutput() && (varp->isScSensitive() || varp->isUsedClock())) {
@@ -291,9 +303,9 @@ class EmitCModel final : public EmitCFunc {
                             UASSERT_OBJ(arrayp->hi() >= arrayp->lo(), varp,
                                         "Should have swapped msb & lsb earlier.");
                             const string ivar = std::string{"__Vi"} + cvtToStr(vecnum);
-                            puts("for (int __Vi" + cvtToStr(vecnum) + "="
+                            puts("for (int __Vi" + cvtToStr(vecnum) + " = "
                                  + cvtToStr(arrayp->lo()));
-                            puts("; " + ivar + "<=" + cvtToStr(arrayp->hi()));
+                            puts("; " + ivar + " <= " + cvtToStr(arrayp->hi()));
                             puts("; ++" + ivar + ") {\n");
                         }
                         puts("sensitive << " + varp->nameProtect());
@@ -324,97 +336,43 @@ class EmitCModel final : public EmitCFunc {
         puts("}\n");
     }
 
-    void emitSettleLoop(AstNodeModule* modp, bool initial) {
-        const string topModNameProtected = prefixNameProtect(modp);
-
-        putsDecoration("// Evaluate till stable\n");
-        if (v3Global.rootp()->changeRequest()) {
-            puts("int __VclockLoop = 0;\n");
-            puts("QData __Vchange = 1;\n");
-        }
-        if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n");
-        puts("do {\n");
-        puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ ");
-        puts(initial ? "Initial" : "Clock");
-        puts(" loop\\n\"););\n");
-        if (initial)
-            puts(topModNameProtected + "__" + protect("_eval_settle") + "(&(vlSymsp->TOP));\n");
-
-        if (v3Global.opt.profExec() && !initial) {
-            puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalLoopBegin();\n");
-        }
-
-        puts(topModNameProtected + "__" + protect("_eval") + "(&(vlSymsp->TOP));\n");
-
-        if (v3Global.opt.profExec() && !initial) {
-            puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalLoopEnd();\n");
-        }
-
-        if (v3Global.rootp()->changeRequest()) {
-            puts("if (VL_UNLIKELY(++__VclockLoop > " + cvtToStr(v3Global.opt.convergeLimit())
-                 + ")) {\n");
-            puts("// About to fail, so enable debug to see what's not settling.\n");
-            puts("// Note you must run make with OPT=-DVL_DEBUG for debug prints.\n");
-            puts("int __Vsaved_debug = Verilated::debug();\n");
-            puts("Verilated::debug(1);\n");
-            puts("__Vchange = " + topModNameProtected + "__" + protect("_change_request")
-                 + "(&(vlSymsp->TOP));\n");
-            puts("Verilated::debug(__Vsaved_debug);\n");
-            puts("VL_FATAL_MT(");
-            putsQuoted(protect(modp->fileline()->filename()));
-            puts(", ");
-            puts(cvtToStr(modp->fileline()->lineno()));
-            puts(", \"\",\n");
-            puts("\"Verilated model didn't ");
-            if (initial) puts("DC ");
-            puts("converge\\n\"\n");
-            puts("\"- See https://verilator.org/warn/DIDNOTCONVERGE\");\n");
-            puts("} else {\n");
-            puts("__Vchange = " + topModNameProtected + "__" + protect("_change_request")
-                 + "(&(vlSymsp->TOP));\n");
-            puts("}\n");
-        }
-        puts("} while ("
-             + (v3Global.rootp()->changeRequest() ? std::string{"VL_UNLIKELY(__Vchange)"}
-                                                  : std::string{"0"})
-             + ");\n");
-    }
-
     void emitStandardMethods1(AstNodeModule* modp) {
         UASSERT_OBJ(modp->isTop(), modp, "Attempting to emitWrapEval for non-top class");
 
         const string topModNameProtected = prefixNameProtect(modp);
         const string selfDecl = "(" + topModNameProtected + "* vlSelf)";
 
-        putSectionDelimiter("Evaluation loop");
+        putSectionDelimiter("Evaluation function");
 
         // Forward declarations
         puts("\n");
-        puts("void " + topModNameProtected + "__" + protect("_eval_initial") + selfDecl + ";\n");
-        puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n");
-        puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n");
-        if (v3Global.rootp()->changeRequest()) {
-            puts("QData " + topModNameProtected + "__" + protect("_change_request") + selfDecl
-                 + ";\n");
-        }
         puts("#ifdef VL_DEBUG\n");
         puts("void " + topModNameProtected + "__" + protect("_eval_debug_assertions") + selfDecl
              + ";\n");
         puts("#endif  // VL_DEBUG\n");
-        puts("void " + topModNameProtected + "__" + protect("_final") + selfDecl + ";\n");
+        puts("void " + topModNameProtected + "__" + protect("_eval_static") + selfDecl + ";\n");
+        puts("void " + topModNameProtected + "__" + protect("_eval_initial") + selfDecl + ";\n");
+        puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n");
+        puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n");
 
-        // _eval_initial_loop
-        puts("\nstatic void " + protect("_eval_initial_loop") + "(" + symClassVar() + ")"
-             + " {\n");
-        puts("vlSymsp->__Vm_didInit = true;\n");
-        puts(topModNameProtected + "__" + protect("_eval_initial") + "(&(vlSymsp->TOP));\n");
-        emitSettleLoop(modp, /* initial: */ true);
-        ensureNewLine();
-        puts("}\n");
-    }
+        if (optSystemC() && v3Global.usesTiming()) {
+            // ::eval
+            puts("\nvoid " + topClassName() + "::eval() {\n");
+            puts("eval_step();\n");
+            puts("if (eventsPending()) {\n");
+            puts("sc_time dt = sc_time::from_value(nextTimeSlot() - contextp()->time());\n");
+            puts("next_trigger(dt, trigger_eval);\n");
+            puts("} else {\n");
+            puts("next_trigger(trigger_eval);\n");
+            puts("}\n");
+            puts("}\n");
+
+            // ::eval_sens
+            puts("\nvoid " + topClassName() + "::eval_sens() {\n");
+            puts("trigger_eval.notify();\n");
+            puts("}\n");
+        }
 
-    void emitStandardMethods2(AstNodeModule* modp) {
-        const string topModNameProtected = prefixNameProtect(modp);
         // ::eval_step
         puts("\nvoid " + topClassName() + "::eval_step() {\n");
         puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName()
@@ -426,9 +384,18 @@ class EmitCModel final : public EmitCFunc {
              + "(&(vlSymsp->TOP));\n");
         puts("#endif  // VL_DEBUG\n");
 
-        putsDecoration("// Initialize\n");
-        puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) " + protect("_eval_initial_loop")
-             + "(vlSymsp);\n");
+        if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n");
+
+        if (v3Global.hasEvents()) puts("vlSymsp->clearTriggeredEvents();\n");
+        if (v3Global.hasClasses()) puts("vlSymsp->__Vm_deleter.deleteAll();\n");
+
+        puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) {\n");
+        puts("vlSymsp->__Vm_didInit = true;\n");
+        puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ Initial\\n\"););\n");
+        puts(topModNameProtected + "__" + protect("_eval_static") + "(&(vlSymsp->TOP));\n");
+        puts(topModNameProtected + "__" + protect("_eval_initial") + "(&(vlSymsp->TOP));\n");
+        puts(topModNameProtected + "__" + protect("_eval_settle") + "(&(vlSymsp->TOP));\n");
+        puts("}\n");
 
         if (v3Global.opt.threads() == 1) {
             const uint32_t mtaskId = 0;
@@ -442,7 +409,8 @@ class EmitCModel final : public EmitCFunc {
             puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalBegin();\n");
         }
 
-        emitSettleLoop(modp, /* initial: */ false);
+        puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ Eval\\n\"););\n");
+        puts(topModNameProtected + "__" + protect("_eval") + "(&(vlSymsp->TOP));\n");
 
         putsDecoration("// Evaluate cleanup\n");
         if (v3Global.opt.threads() == 1) {
@@ -454,8 +422,10 @@ class EmitCModel final : public EmitCFunc {
         puts("}\n");
     }
 
-    void emitStandardMethods3(AstNodeModule* modp) {
+    void emitStandardMethods2(AstNodeModule* modp) {
         const string topModNameProtected = prefixNameProtect(modp);
+        const string selfDecl = "(" + topModNameProtected + "* vlSelf)";
+
         // ::eval_end_step
         if (v3Global.needTraceDumper() && !optSystemC()) {
             puts("\nvoid " + topClassName() + "::eval_end_step() {\n");
@@ -469,6 +439,22 @@ class EmitCModel final : public EmitCFunc {
             puts("}\n");
         }
 
+        putSectionDelimiter("Events and timing");
+        if (auto* const delaySchedp = v3Global.rootp()->delaySchedulerp()) {
+            puts("bool " + topClassName() + "::eventsPending() { return !vlSymsp->TOP.");
+            puts(delaySchedp->nameProtect());
+            puts(".empty(); }\n\n");
+            puts("uint64_t " + topClassName() + "::nextTimeSlot() { return vlSymsp->TOP.");
+            puts(delaySchedp->nameProtect());
+            puts(".nextTimeSlot(); }\n");
+        } else {
+            puts("bool " + topClassName() + "::eventsPending() { return false; }\n\n");
+            puts("uint64_t " + topClassName() + "::nextTimeSlot() {\n");
+            puts("VL_FATAL_MT(__FILE__, __LINE__, \"\", \"%Error: No delays in the "
+                 "design\");\n");
+            puts("return 0;\n}\n");
+        }
+
         putSectionDelimiter("Utilities");
 
         if (!optSystemC()) {
@@ -479,9 +465,12 @@ class EmitCModel final : public EmitCFunc {
         }
 
         putSectionDelimiter("Invoke final blocks");
+        // Forward declarations
+        puts("\n");
+        puts("void " + topModNameProtected + "__" + protect("_eval_final") + selfDecl + ";\n");
         // ::final
         puts("\nVL_ATTR_COLD void " + topClassName() + "::final() {\n");
-        puts(/**/ topModNameProtected + "__" + protect("_final") + "(&(vlSymsp->TOP));\n");
+        puts(/**/ topModNameProtected + "__" + protect("_eval_final") + "(&(vlSymsp->TOP));\n");
         puts("}\n");
 
         putSectionDelimiter("Implementations of abstract methods from VerilatedModel\n");
@@ -629,7 +618,6 @@ class EmitCModel final : public EmitCFunc {
         emitDestructorImplementation();
         emitStandardMethods1(modp);
         emitStandardMethods2(modp);
-        emitStandardMethods3(modp);
         if (v3Global.opt.trace()) { emitTraceMethods(modp); }
         if (v3Global.opt.savable()) { emitSerializationFunctions(); }
 
diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp
index d826bf692..756b29096 100644
--- a/src/V3EmitCSyms.cpp
+++ b/src/V3EmitCSyms.cpp
@@ -295,7 +295,7 @@ class EmitCSyms final : EmitCBaseVisitor {
         if (v3Global.opt.vpi()) {
             const string type
                 = (nodep->origModName() == "__BEGIN__") ? "SCOPE_OTHER" : "SCOPE_MODULE";
-            const string name = nodep->scopep()->name() + "__DOT__" + nodep->name();
+            const string name = nodep->scopep()->shortName() + "__DOT__" + nodep->name();
             const string name_dedot = AstNode::dedotName(name);
             const int timeunit = m_modp->timeunit().powerOfTen();
             m_vpiScopeCandidates.insert(
@@ -445,12 +445,16 @@ void EmitCSyms::emitSymHdr() {
         puts("uint32_t __Vm_baseCode = 0;"
              "  ///< Used by trace routines when tracing multiple models\n");
     }
+    if (v3Global.hasEvents()) puts("std::vector __Vm_triggeredEvents;\n");
+    if (v3Global.hasClasses()) puts("VlDeleter __Vm_deleter;\n");
     puts("bool __Vm_didInit = false;\n");
 
     if (v3Global.opt.mtasks()) {
         puts("\n// MULTI-THREADING\n");
         puts("VlThreadPool* const __Vm_threadPoolp;\n");
-        puts("bool __Vm_even_cycle = false;\n");
+        puts("bool __Vm_even_cycle__ico = false;\n");
+        puts("bool __Vm_even_cycle__act = false;\n");
+        puts("bool __Vm_even_cycle__nba = false;\n");
     }
 
     if (v3Global.opt.profExec()) {
@@ -508,6 +512,22 @@ void EmitCSyms::emitSymHdr() {
     puts("\n// METHODS\n");
     puts("const char* name() { return TOP.name(); }\n");
 
+    if (v3Global.hasEvents()) {
+        puts("void enqueueTriggeredEventForClearing(VlEvent& event) {\n");
+        puts("#ifdef VL_DEBUG\n");
+        puts("if (VL_UNLIKELY(!event.isTriggered())) {\n");
+        puts("VL_FATAL_MT(__FILE__, __LINE__, __FILE__, \"event passed to "
+             "'enqueueTriggeredEventForClearing' was not triggered\");\n");
+        puts("}\n");
+        puts("#endif\n");
+        puts("__Vm_triggeredEvents.push_back(&event);\n");
+        puts("}\n");
+        puts("void clearTriggeredEvents() {\n");
+        puts("for (const auto eventp : __Vm_triggeredEvents) eventp->clearTriggered();\n");
+        puts("__Vm_triggeredEvents.clear();\n");
+        puts("}\n");
+    }
+
     if (v3Global.needTraceDumper()) {
         if (!optSystemC()) puts("void _traceDump();\n");
         puts("void _traceDumpOpen();\n");
@@ -766,15 +786,14 @@ void EmitCSyms::emitSymImp() {
     if (v3Global.opt.profPgo()) {
         puts("// Configure profiling for PGO\n");
         if (v3Global.opt.mtasks()) {
-            v3Global.rootp()->topModulep()->foreach(
-                [&](const AstExecGraph* execGraphp) {
-                    for (const V3GraphVertex* vxp = execGraphp->depGraphp()->verticesBeginp(); vxp;
-                         vxp = vxp->verticesNextp()) {
-                        const ExecMTask* const mtp = static_cast(vxp);
-                        puts("_vm_pgoProfiler.addCounter(" + cvtToStr(mtp->profilerId()) + ", \""
-                             + mtp->hashName() + "\");\n");
-                    }
-                });
+            v3Global.rootp()->topModulep()->foreach([&](const AstExecGraph* execGraphp) {
+                for (const V3GraphVertex* vxp = execGraphp->depGraphp()->verticesBeginp(); vxp;
+                     vxp = vxp->verticesNextp()) {
+                    const ExecMTask* const mtp = static_cast(vxp);
+                    puts("_vm_pgoProfiler.addCounter(" + cvtToStr(mtp->profilerId()) + ", \""
+                         + mtp->hashName() + "\");\n");
+                }
+            });
         }
     }
 
@@ -846,7 +865,7 @@ void EmitCSyms::emitSymImp() {
 
     if (v3Global.dpi()) {
         m_ofpBase->puts("// Setup export functions\n");
-        m_ofpBase->puts("for (int __Vfinal=0; __Vfinal<2; __Vfinal++) {\n");
+        m_ofpBase->puts("for (int __Vfinal = 0; __Vfinal < 2; ++__Vfinal) {\n");
         for (auto it = m_scopeFuncs.begin(); it != m_scopeFuncs.end(); ++it) {
             AstScopeName* const scopep = it->second.m_scopep;
             AstCFunc* const funcp = it->second.m_cfuncp;
diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp
index c05f02af8..2d00e01a9 100644
--- a/src/V3EmitMk.cpp
+++ b/src/V3EmitMk.cpp
@@ -51,6 +51,10 @@ public:
         of.puts("\n### Switches...\n");
         of.puts("# C11 constructs required?  0/1 (always on now)\n");
         of.puts("VM_C11 = 1\n");
+        of.puts("# Timing enabled?  0/1\n");
+        of.puts("VM_TIMING = ");
+        of.puts(v3Global.usesTiming() ? "1" : "0");
+        of.puts("\n");
         of.puts("# Coverage output mode?  0/1 (from --coverage)\n");
         of.puts("VM_COVERAGE = ");
         of.puts(v3Global.opt.coverage() ? "1" : "0");
@@ -109,6 +113,7 @@ public:
                             putMakeClassEntry(of, v3Global.opt.traceSourceLang() + ".cpp");
                         }
                     }
+                    if (v3Global.usesTiming()) putMakeClassEntry(of, "verilated_timing.cpp");
                     if (v3Global.opt.threads()) putMakeClassEntry(of, "verilated_threads.cpp");
                     if (v3Global.opt.usesProfiler()) {
                         putMakeClassEntry(of, "verilated_profiler.cpp");
diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp
index 624fc3f19..3b2d6a3fd 100644
--- a/src/V3EmitV.cpp
+++ b/src/V3EmitV.cpp
@@ -421,6 +421,16 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor {
         puts(")");
     }
 
+    void visit(AstCMethodHard* nodep) override {
+        iterate(nodep->fromp());
+        puts("." + nodep->name() + "(");
+        for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) {
+            if (pinp != nodep->pinsp()) puts(", ");
+            iterate(pinp);
+        }
+        puts(")");
+    }
+
     // Operators
     virtual void emitVerilogFormat(AstNode* nodep, const string& format, AstNode* lhsp = nullptr,
                                    AstNode* const rhsp = nullptr, AstNode* thsp = nullptr,
@@ -587,7 +597,9 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor {
         } else if (nodep->isRanged()) {
             puts(" [");
             puts(cvtToStr(nodep->hi()));
-            puts(":0] ");
+            puts(":");
+            puts(cvtToStr(nodep->lo()));
+            puts("] ");
         }
     }
     void visit(AstConstDType* nodep) override {
diff --git a/src/V3EmitXml.cpp b/src/V3EmitXml.cpp
index 89690d6de..7d1df2878 100644
--- a/src/V3EmitXml.cpp
+++ b/src/V3EmitXml.cpp
@@ -208,7 +208,6 @@ class EmitXmlFileVisitor final : public VNVisitor {
         } else if (nodep->attrClocker() == VVarAttrClocker::CLOCKER_NO) {
             puts(" clocker=\"false\"");
         }
-        if (nodep->attrClockEn()) puts(" clock_enable=\"true\"");
         if (nodep->attrIsolateAssign()) puts(" isolate_assignments=\"true\"");
         if (nodep->isLatched()) puts(" latched=\"true\"");
         if (nodep->isSigPublic()) puts(" public=\"true\"");
diff --git a/src/V3Error.h b/src/V3Error.h
index 4e1de700c..2aca831d9 100644
--- a/src/V3Error.h
+++ b/src/V3Error.h
@@ -51,13 +51,16 @@ public:
         I_COVERAGE,     // Coverage is on/off from /*verilator coverage_on/off*/
         I_TRACING,      // Tracing is on/off from /*verilator tracing_on/off*/
         I_LINT,         // All lint messages
+        I_UNUSED,       // Unused genvar, parameter or signal message (Backward Compatibility)
         I_DEF_NETTYPE_WIRE,  // `default_nettype is WIRE (false=NONE)
+        I_TIMING,       // Enable timing from /*verilator timing_on/off*/
         // Error codes:
-        E_DETECTARRAY,  // Error: Unsupported: Can't detect changes on arrayed variable
         E_ENCAPSULATED, // Error: local/protected violation
         E_PORTSHORT,    // Error: Output port is connected to a constant, electrical short
         E_UNSUPPORTED,  // Error: Unsupported (generally)
         E_TASKNSVAR,    // Error: Task I/O not simple
+        E_NEEDTIMINGOPT,  // Error: --timing/--no-timing option not specified
+        E_NOTIMING,     // Timing control encountered with --no-timing
         //
         // Warning codes:
         EC_FIRST_WARN,  // Just a code so the program knows where to start warnings
@@ -76,7 +79,7 @@ public:
         CASEX,          // Casex
         CASTCONST,      // Cast is constant
         CDCRSTLOGIC,    // Logic in async reset path
-        CLKDATA,        // Clock used as data
+        CLKDATA,        // Clock used as data. Historical, never issued.
         CMPCONST,       // Comparison is constant due to limited range
         COLONPLUS,      // :+ instead of +:
         COMBDLY,        // Combinatorial delayed assignment
@@ -84,13 +87,15 @@ public:
         DEFPARAM,       // Style: Defparam
         DECLFILENAME,   // Declaration doesn't match filename
         DEPRECATED,     // Feature will be deprecated
+        RISEFALLDLY,    // Unsupported: rise/fall/turn-off delays
+        MINTYPMAXDLY,   // Unsupported: min/typ/max delay expressions
         ENDLABEL,       // End lable name mismatch
         EOFNEWLINE,     // End-of-file missing newline
-        GENCLK,         // Generated Clock
+        GENCLK,         // Generated Clock. Historical, never issued.
         HIERBLOCK,      // Ignored hierarchical block setting
         IFDEPTH,        // If statements too deep
         IGNOREDRETURN,  // Ignoring return value (function as task)
-        IMPERFECTSCH,   // Imperfect schedule (disabled by default)
+        IMPERFECTSCH,   // Imperfect schedule (disabled by default). Historical, never issued.
         IMPLICIT,       // Implicit wire
         IMPORTSTAR,     // Import::* in $unit
         IMPURE,         // Impure function not being inlined
@@ -125,19 +130,23 @@ public:
         TICKCOUNT,      // Too large tick count
         TIMESCALEMOD,   // Need timescale for module
         UNDRIVEN,       // No drivers
-        UNOPT,          // Unoptimizable block
+        UNOPT,          // Unoptimizable block. Historical, never issued.
         UNOPTFLAT,      // Unoptimizable block after flattening
         UNOPTTHREADS,   // Thread partitioner unable to fill all requested threads
         UNPACKED,       // Unsupported unpacked
         UNSIGNED,       // Comparison is constant due to unsigned arithmetic
-        UNUSED,         // No receivers
+        UNUSEDGENVAR,   // No receivers for genvar
+        UNUSEDPARAM,    // No receivers for parameters
+        UNUSEDSIGNAL,   // No receivers for signals
         USERERROR,      // Elaboration time $error
         USERFATAL,      // Elaboration time $fatal
         USERINFO,       // Elaboration time $info
         USERWARN,       // Elaboration time $warning
         VARHIDDEN,      // Hiding variable
+        WAITCONST,      // Wait condition is constant
         WIDTH,          // Width mismatch
         WIDTHCONCAT,    // Unsized numbers/parameters in concatenations
+        ZERODLY,        // #0 delay
         _ENUM_MAX
         // ***Add new elements below also***
     };
@@ -151,23 +160,23 @@ public:
     explicit V3ErrorCode(const char* msgp);  // Matching code or ERROR
     explicit V3ErrorCode(int _e)
         : m_e(static_cast(_e)) {}  // Need () or GCC 4.8 false warning
-    constexpr operator en() const { return m_e; }
-    const char* ascii() const {
+    constexpr operator en() const VL_MT_SAFE { return m_e; }
+    const char* ascii() const VL_MT_SAFE {
         // clang-format off
         static const char* const names[] = {
             // Leading spaces indicate it can't be disabled.
             " MIN", " INFO", " FATAL", " FATALEXIT", " FATALSRC", " ERROR", " FIRST_NAMED",
             // Boolean
-            " I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_DEF_NETTYPE_WIRE",
+            " I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_UNUSED", " I_DEF_NETTYPE_WIRE", " I_TIMING",
             // Errors
-            "DETECTARRAY", "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR",
+            "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR", "NEEDTIMINGOPT", "NOTIMING",
             // Warnings
             " EC_FIRST_WARN",
             "ALWCOMBORDER", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA",
             "BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE",
             "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA",
             "CMPCONST", "COLONPLUS", "COMBDLY", "CONTASSREG",
-            "DEFPARAM", "DECLFILENAME", "DEPRECATED",
+            "DEFPARAM", "DECLFILENAME", "DEPRECATED", "RISEFALLDLY", "MINTYPMAXDLY",
             "ENDLABEL", "EOFNEWLINE", "GENCLK", "HIERBLOCK",
             "IFDEPTH", "IGNOREDRETURN",
             "IMPERFECTSCH", "IMPLICIT", "IMPORTSTAR", "IMPURE",
@@ -179,33 +188,34 @@ public:
             "SELRANGE", "SHORTREAL", "SPLITVAR", "STMTDLY", "SYMRSVDWORD", "SYNCASYNCNET",
             "TICKCOUNT", "TIMESCALEMOD",
             "UNDRIVEN", "UNOPT", "UNOPTFLAT", "UNOPTTHREADS",
-            "UNPACKED", "UNSIGNED", "UNUSED",
+            "UNPACKED", "UNSIGNED", "UNUSEDGENVAR", "UNUSEDPARAM", "UNUSEDSIGNAL",
             "USERERROR", "USERFATAL", "USERINFO", "USERWARN",
-            "VARHIDDEN", "WIDTH", "WIDTHCONCAT",
+            "VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "ZERODLY",
             " MAX"
         };
         // clang-format on
         return names[m_e];
     }
     // Warnings that default to off
-    bool defaultsOff() const {
+    bool defaultsOff() const VL_MT_SAFE {
         return (m_e == IMPERFECTSCH || m_e == I_CELLDEFINE || styleError());
     }
     // Warnings that warn about nasty side effects
-    bool dangerous() const { return (m_e == COMBDLY); }
+    bool dangerous() const VL_MT_SAFE { return (m_e == COMBDLY); }
     // Warnings we'll present to the user as errors
     // Later -Werror- options may make more of these.
-    bool pretendError() const {
+    bool pretendError() const VL_MT_SAFE {
         return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT
                 || m_e == CONTASSREG || m_e == IMPURE || m_e == PINNOTFOUND || m_e == PKGNODECL
-                || m_e == PROCASSWIRE);  // Says IEEE
+                || m_e == PROCASSWIRE  // Says IEEE
+                || m_e == ZERODLY);
     }
     // Warnings to mention manual
-    bool mentionManual() const {
+    bool mentionManual() const VL_MT_SAFE {
         return (m_e == EC_FATALSRC || m_e == SYMRSVDWORD || pretendError());
     }
     // Warnings that are lint only
-    bool lintError() const {
+    bool lintError() const VL_MT_SAFE {
         return (m_e == ALWCOMBORDER || m_e == BSSPACE || m_e == CASEINCOMPLETE
                 || m_e == CASEOVERLAP || m_e == CASEWITHX || m_e == CASEX || m_e == CASTCONST
                 || m_e == CMPCONST || m_e == COLONPLUS || m_e == ENDLABEL || m_e == IMPLICIT
@@ -213,13 +223,19 @@ public:
                 || m_e == UNSIGNED || m_e == WIDTH);
     }
     // Warnings that are style only
-    bool styleError() const {
+    bool styleError() const VL_MT_SAFE {
         return (m_e == ASSIGNDLY  // More than style, but for backward compatibility
                 || m_e == BLKSEQ || m_e == DEFPARAM || m_e == DECLFILENAME || m_e == EOFNEWLINE
                 || m_e == IMPORTSTAR || m_e == INCABSPATH || m_e == PINCONNECTEMPTY
-                || m_e == PINNOCONNECT || m_e == SYNCASYNCNET || m_e == UNDRIVEN || m_e == UNUSED
+                || m_e == PINNOCONNECT || m_e == SYNCASYNCNET || m_e == UNDRIVEN
+                || m_e == UNUSEDGENVAR || m_e == UNUSEDPARAM || m_e == UNUSEDSIGNAL
                 || m_e == VARHIDDEN);
     }
+    // Warnings that are unused only
+    bool unusedError() const VL_MT_SAFE {
+        return (m_e == UNUSEDGENVAR || m_e == UNUSEDPARAM || m_e == UNUSEDSIGNAL);
+    }
+    static bool unusedMsg(const char* msgp) { return 0 == VL_STRCASECMP(msgp, "UNUSED"); }
 };
 constexpr bool operator==(const V3ErrorCode& lhs, const V3ErrorCode& rhs) {
     return lhs.m_e == rhs.m_e;
@@ -269,15 +285,15 @@ public:
     // CONSTRUCTORS
     // ACCESSORS
     static void debugDefault(int level) { s_debugDefault = level; }
-    static int debugDefault() { return s_debugDefault; }
+    static int debugDefault() VL_MT_SAFE { return s_debugDefault; }
     static void errorLimit(int level) { s_errorLimit = level; }
-    static int errorLimit() { return s_errorLimit; }
+    static int errorLimit() VL_MT_SAFE { return s_errorLimit; }
     static void warnFatal(bool flag) { s_warnFatal = flag; }
     static bool warnFatal() { return s_warnFatal; }
     static string msgPrefix();  // returns %Error/%Warn
-    static int errorCount() { return s_errCount; }
+    static int errorCount() VL_MT_SAFE { return s_errCount; }
     static int warnCount() { return s_warnCount; }
-    static bool errorContexted() { return s_errorContexted; }
+    static bool errorContexted() VL_MT_SAFE { return s_errorContexted; }
     static void errorContexted(bool flag) { s_errorContexted = flag; }
     // METHODS
     static void incErrors();
@@ -291,7 +307,7 @@ public:
     static void pretendError(V3ErrorCode code, bool flag) { s_pretendError[code] = flag; }
     static bool isError(V3ErrorCode code, bool supp);
     static string lineStr(const char* filename, int lineno);
-    static V3ErrorCode errorCode() { return s_errorCode; }
+    static V3ErrorCode errorCode() VL_MT_SAFE { return s_errorCode; }
     static void errorExitCb(ErrorExitCb cb) { s_errorExitCb = cb; }
 
     // When printing an error/warning, print prefix for multiline message
@@ -322,7 +338,7 @@ inline void v3errorEnd(std::ostringstream& sstr) { V3Error::v3errorEnd(sstr); }
 inline void v3errorEndFatal(std::ostringstream& sstr) {
     V3Error::v3errorEnd(sstr);
     assert(0);  // LCOV_EXCL_LINE
-    VL_UNREACHABLE
+    VL_UNREACHABLE;
 }
 
 // Theses allow errors using << operators: v3error("foo"<<"bar");
@@ -450,6 +466,7 @@ inline void v3errorEndFatal(std::ostringstream& sstr) {
 #define VL_DEFINE_DEBUG_FUNCTIONS \
     VL_DEFINE_DEBUG(); /* Define 'int debug()' */ \
     VL_DEFINE_DUMP(); /* Define 'int dump()' */ \
+    VL_DEFINE_DUMP(Dfg); /* Define 'int dumpDfg()' */ \
     VL_DEFINE_DUMP(Graph); /* Define 'int dumpGraph()' */ \
     VL_DEFINE_DUMP(Tree); /* Define 'int dumpTree()' */ \
     static_assert(true, "")
diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp
index 2e1c76afe..3704d4631 100644
--- a/src/V3FileLine.cpp
+++ b/src/V3FileLine.cpp
@@ -155,7 +155,7 @@ void VFileContent::pushText(const string& text) {
     m_lines.emplace_back(string(leftover, line_start));  // Might be ""
 }
 
-string VFileContent::getLine(int lineno) const {
+string VFileContent::getLine(int lineno) const VL_MT_SAFE {
     // Return error text rather than asserting so the user isn't left without a message
     // cppcheck-suppress negativeContainerIndex
     if (VL_UNCOVERABLE(lineno < 0 || lineno >= (int)m_lines.size())) {
@@ -284,7 +284,7 @@ FileLine* FileLine::copyOrSameFileLine() {
     return newp;
 }
 
-string FileLine::filebasename() const {
+string FileLine::filebasename() const VL_MT_SAFE {
     string name = filename();
     string::size_type pos;
     if ((pos = name.rfind('/')) != string::npos) name.erase(0, pos + 1);
@@ -298,7 +298,7 @@ string FileLine::filebasenameNoExt() const {
     return name;
 }
 
-string FileLine::firstColumnLetters() const {
+string FileLine::firstColumnLetters() const VL_MT_SAFE {
     const char a = ((firstColumn() / 26) % 26) + 'a';
     const char b = (firstColumn() % 26) + 'a';
     return string(1, a) + string(1, b);
@@ -322,7 +322,7 @@ string FileLine::asciiLineCol() const {
             + "-" + cvtToStr(lastColumn()) + "[" + (m_contentp ? m_contentp->ascii() : "ct0") + "+"
             + cvtToStr(m_contentLineno) + "]");
 }
-string FileLine::ascii() const {
+string FileLine::ascii() const VL_MT_SAFE {
     // For most errors especially in the parser the lastLineno is more accurate than firstLineno
     return filename() + ":" + cvtToStr(lastLineno()) + ":" + cvtToStr(firstColumn());
 }
@@ -332,7 +332,15 @@ std::ostream& operator<<(std::ostream& os, FileLine* fileline) {
 }
 
 bool FileLine::warnOff(const string& msg, bool flag) {
-    const V3ErrorCode code(msg.c_str());
+    const char* cmsg = msg.c_str();
+    // Backward compatibility with msg="UNUSED"
+    if (V3ErrorCode::unusedMsg(cmsg)) {
+        warnOff(V3ErrorCode::UNUSEDGENVAR, flag);
+        warnOff(V3ErrorCode::UNUSEDPARAM, flag);
+        warnOff(V3ErrorCode::UNUSEDSIGNAL, flag);
+        return true;
+    }
+    const V3ErrorCode code(cmsg);
     if (code < V3ErrorCode::EC_FIRST_WARN) {
         return false;
     } else {
@@ -355,14 +363,19 @@ void FileLine::warnStyleOff(bool flag) {
     }
 }
 
-bool FileLine::warnIsOff(V3ErrorCode code) const {
+void FileLine::warnUnusedOff(bool flag) {
+    warnOff(V3ErrorCode::UNUSEDGENVAR, flag);
+    warnOff(V3ErrorCode::UNUSEDPARAM, flag);
+    warnOff(V3ErrorCode::UNUSEDSIGNAL, flag);
+}
+
+bool FileLine::warnIsOff(V3ErrorCode code) const VL_MT_SAFE {
     if (!msgEn().test(code)) return true;
     if (!defaultFileLine().msgEn().test(code)) return true;  // Global overrides local
-    // UNOPTFLAT implies UNOPT
-    if (code == V3ErrorCode::UNOPT && !msgEn().test(V3ErrorCode::UNOPTFLAT)) return true;
     if ((code.lintError() || code.styleError()) && !msgEn().test(V3ErrorCode::I_LINT)) {
         return true;
     }
+    if ((code.unusedError()) && !msgEn().test(V3ErrorCode::I_UNUSED)) { return true; }
     return false;
 }
 
@@ -393,7 +406,7 @@ string FileLine::warnMore() const {
         return V3Error::warnMore();
     }
 }
-string FileLine::warnOther() const {
+string FileLine::warnOther() const VL_MT_SAFE {
     if (lastLineno()) {
         return V3Error::warnMore() + ascii() + ": ";
     } else {
@@ -401,7 +414,7 @@ string FileLine::warnOther() const {
     }
 }
 
-string FileLine::source() const {
+string FileLine::source() const VL_MT_SAFE {
     if (VL_UNCOVERABLE(!m_contentp)) {  // LCOV_EXCL_START
         if (debug() || v3Global.opt.debugCheck()) {
             // The newline here is to work around the "  | "
@@ -412,7 +425,7 @@ string FileLine::source() const {
     }  // LCOV_EXCL_STOP
     return m_contentp->getLine(m_contentLineno);
 }
-string FileLine::prettySource() const {
+string FileLine::prettySource() const VL_MT_SAFE {
     string out = source();
     // Drop ignore trailing newline
     const string::size_type pos = out.find('\n');
@@ -421,7 +434,7 @@ string FileLine::prettySource() const {
     return VString::spaceUnprintable(out);
 }
 
-string FileLine::warnContext(bool secondary) const {
+string FileLine::warnContext(bool secondary) const VL_MT_SAFE {
     V3Error::errorContexted(true);
     if (!v3Global.opt.context()) return "";
     string out;
diff --git a/src/V3FileLine.h b/src/V3FileLine.h
index c8a44a0bf..b846bba22 100644
--- a/src/V3FileLine.h
+++ b/src/V3FileLine.h
@@ -86,7 +86,9 @@ class FileLineSingleton final {
     // Return index to intersection set
     msgEnSetIdx_t msgEnAnd(msgEnSetIdx_t lhsIdx, msgEnSetIdx_t rhsIdx);
     // Retrieve interned bitset at given interned index. The returned reference is not persistent.
-    const MsgEnBitSet& msgEn(msgEnSetIdx_t idx) const { return m_internedMsgEns.at(idx); }
+    const MsgEnBitSet& msgEn(msgEnSetIdx_t idx) const VL_MT_SAFE {
+        return m_internedMsgEns.at(idx);
+    }
 };
 
 // All source lines from a file/stream, to enable errors to show sources
@@ -110,7 +112,7 @@ class VFileContent final {
 
 public:
     void pushText(const string& text);  // Add arbitrary text (need not be line-by-line)
-    string getLine(int lineno) const;
+    string getLine(int lineno) const VL_MT_SAFE;
     string ascii() const { return "ct" + cvtToStr(m_id); }
 };
 std::ostream& operator<<(std::ostream& os, VFileContent* contentp);
@@ -228,29 +230,31 @@ public:
     }
     // Advance last line/column based on given text
     void forwardToken(const char* textp, size_t size, bool trackLines = true);
-    int firstLineno() const { return m_firstLineno; }
-    int firstColumn() const { return m_firstColumn; }
-    int lastLineno() const { return m_lastLineno; }
-    int lastColumn() const { return m_lastColumn; }
+    int firstLineno() const VL_MT_SAFE { return m_firstLineno; }
+    int firstColumn() const VL_MT_SAFE { return m_firstColumn; }
+    int lastLineno() const VL_MT_SAFE { return m_lastLineno; }
+    int lastColumn() const VL_MT_SAFE { return m_lastColumn; }
     VFileContent* contentp() const { return m_contentp; }
     // If not otherwise more specific, use last lineno for errors etc,
     // as the parser errors etc generally make more sense pointing at the last parse point
-    int lineno() const { return m_lastLineno; }
-    string source() const;
-    string prettySource() const;  // Source, w/stripped unprintables and newlines
-    FileLine* parent() const { return m_parent; }
+    int lineno() const VL_MT_SAFE { return m_lastLineno; }
+    string source() const VL_MT_SAFE;
+    string prettySource() const VL_MT_SAFE;  // Source, w/stripped unprintables and newlines
+    FileLine* parent() const VL_MT_SAFE { return m_parent; }
     V3LangCode language() const { return singleton().numberToLang(filenameno()); }
     string ascii() const;
     string asciiLineCol() const;
-    int filenameno() const { return m_filenameno; }
-    string filename() const { return singleton().numberToName(filenameno()); }
-    bool filenameIsGlobal() const {
+    int filenameno() const VL_MT_SAFE { return m_filenameno; }
+    string filename() const VL_MT_SAFE { return singleton().numberToName(filenameno()); }
+    bool filenameIsGlobal() const VL_MT_SAFE {
         return (filename() == commandLineFilename() || filename() == builtInFilename());
     }
-    string filenameLetters() const { return FileLineSingleton::filenameLetters(filenameno()); }
-    string filebasename() const;
+    string filenameLetters() const VL_MT_SAFE {
+        return FileLineSingleton::filenameLetters(filenameno());
+    }
+    string filebasename() const VL_MT_SAFE;
     string filebasenameNoExt() const;
-    string firstColumnLetters() const;
+    string firstColumnLetters() const VL_MT_SAFE;
     string profileFuncname() const;
     string xmlDetailedLocation() const;
     string lineDirectiveStrg(int enterExit) const;
@@ -264,6 +268,7 @@ public:
     bool warnIsOff(V3ErrorCode code) const;
     void warnLintOff(bool flag);
     void warnStyleOff(bool flag);
+    void warnUnusedOff(bool flag);
     void warnStateFrom(const FileLine& from) { m_msgEnIdx = from.m_msgEnIdx; }
     void warnResetDefault() { warnStateFrom(defaultFileLine()); }
     bool lastWarnWaived() const { return m_waive; }
@@ -275,13 +280,16 @@ public:
     void coverageOn(bool flag) { warnOn(V3ErrorCode::I_COVERAGE, flag); }
     bool tracingOn() const { return msgEn().test(V3ErrorCode::I_TRACING); }
     void tracingOn(bool flag) { warnOn(V3ErrorCode::I_TRACING, flag); }
+    bool timingOn() const { return msgEn().test(V3ErrorCode::I_TIMING); }
+    void timingOn(bool flag) { warnOn(V3ErrorCode::I_TIMING, flag); }
 
     // METHODS - Global
     //  and  match what GCC outputs
-    static string commandLineFilename() { return ""; }
-    static string builtInFilename() { return ""; }
+    static string commandLineFilename() VL_MT_SAFE { return ""; }
+    static string builtInFilename() VL_MT_SAFE { return ""; }
     static void globalWarnLintOff(bool flag) { defaultFileLine().warnLintOff(flag); }
     static void globalWarnStyleOff(bool flag) { defaultFileLine().warnStyleOff(flag); }
+    static void globalWarnUnusedOff(bool flag) { defaultFileLine().warnUnusedOff(flag); }
     static void globalWarnOff(V3ErrorCode code, bool flag) {
         defaultFileLine().warnOff(code, flag);
     }
@@ -305,20 +313,20 @@ public:
     void v3errorEndFatal(std::ostringstream& str) VL_ATTR_NORETURN {
         v3errorEnd(str);
         assert(0);  // LCOV_EXCL_LINE
-        VL_UNREACHABLE
+        VL_UNREACHABLE;
     }
     /// When building an error, prefix for printing continuation lines
     /// e.g. information referring to the same FileLine as before
     string warnMore() const;
     /// When building an error, prefix for printing secondary information
     /// from a different FileLine than the original error
-    string warnOther() const;
+    string warnOther() const VL_MT_SAFE;
     /// When building an error, current location in include etc
     /// If not used in a given error, automatically pasted at end of error
-    string warnContextPrimary() const { return warnContext(false); }
+    string warnContextPrimary() const VL_MT_SAFE { return warnContext(false); }
     /// When building an error, additional location for additional references
     /// Simplified information vs warnContextPrimary() to make dump clearer
-    string warnContextSecondary() const { return warnContext(true); }
+    string warnContextSecondary() const VL_MT_SAFE { return warnContext(true); }
     bool operator==(const FileLine& rhs) const {
         return (m_firstLineno == rhs.m_firstLineno && m_firstColumn == rhs.m_firstColumn
                 && m_lastLineno == rhs.m_lastLineno && m_lastColumn == rhs.m_lastColumn
@@ -341,8 +349,8 @@ public:
     }
 
 private:
-    string warnContext(bool secondary) const;
-    const MsgEnBitSet& msgEn() const { return singleton().msgEn(m_msgEnIdx); }
+    string warnContext(bool secondary) const VL_MT_SAFE;
+    const MsgEnBitSet& msgEn() const VL_MT_SAFE { return singleton().msgEn(m_msgEnIdx); }
 };
 std::ostream& operator<<(std::ostream& os, FileLine* fileline);
 
diff --git a/src/V3Force.cpp b/src/V3Force.cpp
index bebf210ac..a1c136f6b 100644
--- a/src/V3Force.cpp
+++ b/src/V3Force.cpp
@@ -153,7 +153,7 @@ class ForceConvertVisitor final : public VNVisitor {
     // referenced AstVarScope with the given function.
     void transformWritenVarScopes(AstNode* nodep, std::function f) {
         UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced");
-        nodep->foreach([&f](AstNodeVarRef* refp) {
+        nodep->foreach([&f](AstNodeVarRef* refp) {
             if (refp->access() != VAccess::WRITE) return;
             // TODO: this is not strictly speaking safe for some complicated lvalues, eg.:
             //       'force foo[a(cnt)] = 1;', where 'cnt' is an out parameter, but it will
@@ -230,7 +230,7 @@ class ForceConvertVisitor final : public VNVisitor {
         AstAssign* const resetRdp
             = new AstAssign{fl_nowarn, lhsp->cloneTree(false), lhsp->unlinkFrBack()};
         // Replace write refs on the LHS
-        resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) {
+        resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) {
             if (refp->access() != VAccess::WRITE) return;
             AstVarScope* const vscp = refp->varScopep();
             AstVarScope* const newVscp
@@ -243,7 +243,7 @@ class ForceConvertVisitor final : public VNVisitor {
             VL_DO_DANGLING(refp->deleteTree(), refp);
         });
         // Replace write refs on RHS
-        resetRdp->rhsp()->foreach([this](AstNodeVarRef* refp) {
+        resetRdp->rhsp()->foreach([this](AstNodeVarRef* refp) {
             if (refp->access() != VAccess::WRITE) return;
             AstVarScope* const vscp = refp->varScopep();
             AstVarScope* const newVscp
@@ -273,7 +273,7 @@ class ForceConvertVisitor final : public VNVisitor {
         iterateAndNextNull(nodep->modulesp());
 
         // Replace references to forced signals
-        nodep->modulesp()->foreachAndNext([this](AstVarRef* nodep) {
+        nodep->modulesp()->foreachAndNext([this](AstVarRef* nodep) {
             if (ForceComponentsVarScope* const fcp
                 = m_forceComponentsVarScope.tryGet(nodep->varScopep())) {
                 switch (nodep->access()) {
diff --git a/src/V3FunctionTraits.h b/src/V3FunctionTraits.h
new file mode 100644
index 000000000..6f8a854e0
--- /dev/null
+++ b/src/V3FunctionTraits.h
@@ -0,0 +1,50 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Function traits for metaprogramming
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3FUNCTIONTRAITS_H_
+#define VERILATOR_V3FUNCTIONTRAITS_H_
+
+#include "verilatedos.h"
+
+#include 
+#include 
+#include 
+
+template 
+struct FunctionTraits;
+
+// For generic types, directly use the result of the signature of its 'operator()'
+template 
+struct FunctionTraits final
+    : public FunctionTraits::type::operator())> {};
+
+// Specialization for pointers to member function
+template 
+struct FunctionTraits VL_NOT_FINAL {
+    // Number of arguments
+    static constexpr size_t arity = sizeof...(Args);
+
+    // Type of result
+    using result_type = ReturnType;
+
+    // Type of arguments
+    template 
+    struct arg {
+        using type = typename std::tuple_element>::type;
+    };
+};
+
+#endif
diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp
index 6829d55ee..cb5a1f498 100644
--- a/src/V3Gate.cpp
+++ b/src/V3Gate.cpp
@@ -239,6 +239,8 @@ private:
         m_substTreep = nodep->rhsp();
         if (!VN_IS(nodep->lhsp(), NodeVarRef)) {
             clearSimple("ASSIGN(non-VARREF)");
+        } else if (nodep->isTimingControl()) {
+            clearSimple("Timing control");
         } else {
             iterateChildren(nodep);
         }
@@ -335,6 +337,13 @@ private:
     VDouble0 m_statAssignMerged;  // Statistic tracking
 
     // METHODS
+    void checkTimingControl(AstNode* nodep) {
+        if (nodep->isTimingControl() && m_logicVertexp) {
+            m_logicVertexp->clearReducibleAndDedupable("TimingControl");
+            m_logicVertexp->setConsumed("TimingControl");
+        }
+    }
+
     void iterateNewStmt(AstNode* nodep, const char* nonReducibleReason,
                         const char* consumeReason) {
         if (m_scopep) {
@@ -349,6 +358,7 @@ private:
             }
             if (consumeReason) m_logicVertexp->setConsumed(consumeReason);
             if (VN_IS(nodep, SenItem)) m_logicVertexp->setConsumed("senItem");
+            checkTimingControl(nodep);
             iterateChildren(nodep);
             m_logicVertexp = nullptr;
         }
@@ -544,6 +554,7 @@ private:
     void visit(AstNode* nodep) override {
         iterateChildren(nodep);
         if (nodep->isOutputter() && m_logicVertexp) m_logicVertexp->setConsumed("outputter");
+        checkTimingControl(nodep);
     }
 
 public:
@@ -1011,13 +1022,13 @@ static void eliminate(AstNode* logicp,
         nodep->replaceWith(newp);
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
         // Recursively substitute the new tree
-        newp->foreach(visit);
+        newp->foreach(visit);
 
         // Remove from recursion filter
         replaced.erase(vscp);
     };
 
-    logicp->foreach(visit);
+    logicp->foreach(visit);
 }
 
 // ######################################################################
diff --git a/src/V3GenClk.cpp b/src/V3GenClk.cpp
deleted file mode 100644
index d4b507238..000000000
--- a/src/V3GenClk.cpp
+++ /dev/null
@@ -1,219 +0,0 @@
-// -*- mode: C++; c-file-style: "cc-mode" -*-
-//*************************************************************************
-// DESCRIPTION: Verilator: Generated Clock repairs
-//
-// Code available from: https://verilator.org
-//
-//*************************************************************************
-//
-// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
-// can redistribute it and/or modify it under the terms of either the GNU
-// Lesser General Public License Version 3 or the Perl Artistic License
-// Version 2.0.
-// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
-//
-//*************************************************************************
-// GENCLK TRANSFORMATIONS:
-//      Follow control-flow graph with assignments and var usages
-//          ASSIGNDLY to variable later used as clock requires change detect
-//
-//*************************************************************************
-
-#include "config_build.h"
-#include "verilatedos.h"
-
-#include "V3GenClk.h"
-
-#include "V3Ast.h"
-#include "V3Global.h"
-
-VL_DEFINE_DEBUG_FUNCTIONS;
-
-//######################################################################
-// GenClk Read
-
-class GenClkRenameVisitor final : public VNVisitor {
-private:
-    // NODE STATE
-    // Cleared on top scope
-    //  AstVarScope::user2()    -> AstVarScope*.  Signal replacing activation with
-    //  AstVarRef::user3()      -> bool.  Signal is replaced activation (already done)
-    const VNUser2InUse m_inuser2;
-    const VNUser3InUse m_inuser3;
-
-    // STATE
-    const AstActive* m_activep = nullptr;  // Inside activate statement
-    AstNodeModule* const m_topModp;  // Top module
-    AstScope* const m_scopetopp = v3Global.rootp()->topScopep()->scopep();  // The top AstScope
-
-    // METHODS
-    AstVarScope* genInpClk(AstVarScope* vscp) {
-        if (!vscp->user2p()) {
-            // In order to create a __VinpClk* for a signal, it needs to be marked circular.
-            // The DPI export trigger is never marked circular by V3Order (see comments in
-            // OrderVisitor::nodeMarkCircular). The only other place where one might mark
-            // a node circular is in this pass (V3GenClk), if the signal is assigned but was
-            // previously used as a clock. The DPI export trigger is only ever assigned in
-            // a DPI export called from outside eval, or from a DPI import, which are not
-            // discovered by GenClkReadVisitor (note that impure tasks - i.e.: those setting
-            // non-local variables - cannot be no-inline, see V3Task), hence the DPI export
-            // trigger should never be marked circular. Note that ordering should still be
-            // correct as there will be a change detect on any signals set from a DPI export
-            // that might have dependents scheduled earlier.
-            UASSERT_OBJ(vscp != v3Global.rootp()->dpiExportTriggerp(), vscp,
-                        "DPI export trigger should not need __VinpClk");
-            AstVar* const varp = vscp->varp();
-            const string newvarname
-                = "__VinpClk__" + vscp->scopep()->nameDotless() + "__" + varp->name();
-            // Create:  VARREF(inpclk)
-            //          ...
-            //          ASSIGN(VARREF(inpclk), VARREF(var))
-            AstVar* const newvarp
-                = new AstVar(varp->fileline(), VVarType::MODULETEMP, newvarname, varp);
-            m_topModp->addStmtsp(newvarp);
-            AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopetopp, newvarp);
-            m_scopetopp->addVarsp(newvscp);
-            AstAssign* const asninitp = new AstAssign(
-                vscp->fileline(), new AstVarRef(vscp->fileline(), newvscp, VAccess::WRITE),
-                new AstVarRef(vscp->fileline(), vscp, VAccess::READ));
-            m_scopetopp->addFinalClksp(asninitp);
-            //
-            vscp->user2p(newvscp);
-        }
-        return VN_AS(vscp->user2p(), VarScope);
-    }
-
-    // VISITORS
-    void visit(AstVarRef* nodep) override {
-        // Consumption/generation of a variable,
-        if (m_activep && !nodep->user3SetOnce()) {
-            AstVarScope* const vscp = nodep->varScopep();
-            if (vscp->isCircular()) {
-                UINFO(8, "  VarActReplace " << nodep << endl);
-                // Replace with the new variable
-                AstVarScope* const newvscp = genInpClk(vscp);
-                AstVarRef* const newrefp
-                    = new AstVarRef(nodep->fileline(), newvscp, nodep->access());
-                nodep->replaceWith(newrefp);
-                VL_DO_DANGLING(pushDeletep(nodep), nodep);
-            }
-        }
-    }
-    void visit(AstActive* nodep) override {
-        m_activep = nodep;
-        iterate(nodep->sensesp());
-        m_activep = nullptr;
-    }
-
-    //-----
-    void visit(AstNode* nodep) override { iterateChildren(nodep); }
-
-public:
-    // CONSTRUCTORS
-    GenClkRenameVisitor(AstTopScope* nodep, AstNodeModule* topModp)
-        : m_topModp{topModp} {
-        iterate(nodep);
-    }
-    ~GenClkRenameVisitor() override = default;
-};
-
-//######################################################################
-// GenClk Read
-
-class GenClkReadVisitor final : public VNVisitor {
-private:
-    // NODE STATE
-    // Cleared on top scope
-    //  AstVarScope::user()     -> bool.  Set when the var has been used as clock
-
-    // STATE
-    bool m_tracingCall = false;  // Iterating into a call to a cfunc
-    const AstActive* m_activep = nullptr;  // Inside activate statement
-    const AstNodeAssign* m_assignp = nullptr;  // Inside assigndly statement
-    AstNodeModule* m_topModp = nullptr;  // Top module
-
-    // VISITORS
-    void visit(AstTopScope* nodep) override {
-        {
-            const VNUser1InUse user1InUse;
-            iterateChildren(nodep);
-        }
-        // Make the new clock signals and replace any activate references
-        // See rename, it does some AstNode::userClearTree()'s
-        GenClkRenameVisitor{nodep, m_topModp};
-    }
-    void visit(AstNodeModule* nodep) override {
-        // Only track the top scopes, not lower level functions
-        if (nodep->isTop()) {
-            m_topModp = nodep;
-            iterateChildren(nodep);
-        }
-    }
-    void visit(AstNodeCCall* nodep) override {
-        iterateChildren(nodep);
-        if (!nodep->funcp()->entryPoint()) {
-            // Enter the function and trace it
-            m_tracingCall = true;
-            iterate(nodep->funcp());
-        }
-    }
-    void visit(AstCFunc* nodep) override {
-        if (!m_tracingCall && !nodep->entryPoint()) {
-            // Only consider logic within a CFunc when looking
-            // at the call to it, and not when scanning whatever
-            // scope it happens to live beneath.
-            return;
-        }
-        m_tracingCall = false;
-        iterateChildren(nodep);
-    }
-    //----
-
-    void visit(AstVarRef* nodep) override {
-        // Consumption/generation of a variable,
-        AstVarScope* const vscp = nodep->varScopep();
-        UASSERT_OBJ(vscp, nodep, "Scope not assigned");
-        if (m_activep) {
-            UINFO(8, "  VarAct " << nodep << endl);
-            vscp->user1(true);
-        }
-        if (m_assignp && nodep->access().isWriteOrRW() && vscp->user1()) {
-            // Variable was previously used as a clock, and is now being set
-            // Thus a unordered generated clock...
-            UINFO(8, "  VarSetAct " << nodep << endl);
-            vscp->circular(true);
-        }
-    }
-    void visit(AstNodeAssign* nodep) override {
-        // UINFO(8, "ASS " << nodep << endl);
-        m_assignp = nodep;
-        iterateChildren(nodep);
-        m_assignp = nullptr;
-    }
-    void visit(AstActive* nodep) override {
-        UINFO(8, "ACTIVE " << nodep << endl);
-        m_activep = nodep;
-        UASSERT_OBJ(nodep->sensesp(), nodep, "Unlinked");
-        iterate(nodep->sensesp());
-        m_activep = nullptr;
-        iterateChildren(nodep);
-    }
-
-    //-----
-    void visit(AstVar*) override {}  // Don't want varrefs under it
-    void visit(AstNode* nodep) override { iterateChildren(nodep); }
-
-public:
-    // CONSTRUCTORS
-    explicit GenClkReadVisitor(AstNetlist* nodep) { iterate(nodep); }
-    ~GenClkReadVisitor() override = default;
-};
-
-//######################################################################
-// GenClk class functions
-
-void V3GenClk::genClkAll(AstNetlist* nodep) {
-    UINFO(2, __FUNCTION__ << ": " << endl);
-    { GenClkReadVisitor{nodep}; }  // Destruct before checking
-    V3Global::dumpCheckGlobalTree("genclk", 0, dumpTree() >= 3);
-}
diff --git a/src/V3Global.cpp b/src/V3Global.cpp
index 5510386f0..34cbe343b 100644
--- a/src/V3Global.cpp
+++ b/src/V3Global.cpp
@@ -93,8 +93,11 @@ string V3Global::digitsFilename(int number) {
 }
 
 void V3Global::dumpCheckGlobalTree(const string& stagename, int newNumber, bool doDump) {
-    v3Global.rootp()->dumpTreeFile(v3Global.debugFilename(stagename + ".tree", newNumber), false,
-                                   doDump);
+    const string treeFilename = v3Global.debugFilename(stagename + ".tree", newNumber);
+    v3Global.rootp()->dumpTreeFile(treeFilename, false, doDump);
+    if (v3Global.opt.dumpTreeDot()) {
+        v3Global.rootp()->dumpTreeDotFile(treeFilename + ".dot", false, doDump);
+    }
     if (v3Global.opt.stats()) V3Stats::statsStage(stagename);
 }
 
diff --git a/src/V3Global.h b/src/V3Global.h
index 0a50e97b6..7108f4148 100644
--- a/src/V3Global.h
+++ b/src/V3Global.h
@@ -105,6 +105,9 @@ class V3Global final {
     // Experimenting with always requiring heavy, see (#2701)
     bool m_needTraceDumper = false;  // Need __Vm_dumperp in symbols
     bool m_dpi = false;  // Need __Dpi include files
+    bool m_hasEvents = false;  // Design uses SystemVerilog named events
+    bool m_hasClasses = false;  // Design uses SystemVerilog classes
+    bool m_usesTiming = false;  // Design uses timing constructs
     bool m_hasForceableSignals = false;  // Need to apply V3Force pass
     bool m_hasSCTextSections = false;  // Has `systemc_* sections that need to be emitted
     bool m_useParallelBuild = false;  // Use parallel build for model
@@ -124,7 +127,7 @@ public:
     void clear();
     void shutdown();  // Release allocated resorces
     // ACCESSORS (general)
-    AstNetlist* rootp() const { return m_rootp; }
+    AstNetlist* rootp() const VL_MT_SAFE { return m_rootp; }
     VWidthMinUsage widthMinUsage() const { return m_widthMinUsage; }
     bool assertDTypesResolved() const { return m_assertDTypesResolved; }
     bool assertScoped() const { return m_assertScoped; }
@@ -143,11 +146,17 @@ public:
     static string digitsFilename(int number);
     bool needTraceDumper() const { return m_needTraceDumper; }
     void needTraceDumper(bool flag) { m_needTraceDumper = flag; }
-    bool dpi() const { return m_dpi; }
+    bool dpi() const VL_MT_SAFE { return m_dpi; }
     void dpi(bool flag) { m_dpi = flag; }
+    bool hasEvents() const { return m_hasEvents; }
+    void setHasEvents() { m_hasEvents = true; }
+    bool hasClasses() const { return m_hasClasses; }
+    void setHasClasses() { m_hasClasses = true; }
+    bool usesTiming() const { return m_usesTiming; }
+    void setUsesTiming() { m_usesTiming = true; }
     bool hasForceableSignals() const { return m_hasForceableSignals; }
     void setHasForceableSignals() { m_hasForceableSignals = true; }
-    bool hasSCTextSections() const { return m_hasSCTextSections; }
+    bool hasSCTextSections() const VL_MT_SAFE { return m_hasSCTextSections; }
     void setHasSCTextSections() { m_hasSCTextSections = true; }
     V3HierBlockPlan* hierPlanp() const { return m_hierPlanp; }
     void hierPlanp(V3HierBlockPlan* plan) {
diff --git a/src/V3Graph.cpp b/src/V3Graph.cpp
index faffb7b41..4218394fa 100644
--- a/src/V3Graph.cpp
+++ b/src/V3Graph.cpp
@@ -144,7 +144,7 @@ void V3GraphVertex::v3errorEnd(std::ostringstream& str) const {
 void V3GraphVertex::v3errorEndFatal(std::ostringstream& str) const {
     v3errorEnd(str);
     assert(0);  // LCOV_EXCL_LINE
-    VL_UNREACHABLE
+    VL_UNREACHABLE;
 }
 
 std::ostream& operator<<(std::ostream& os, V3GraphVertex* vertexp) {
diff --git a/src/V3GraphAcyc.cpp b/src/V3GraphAcyc.cpp
index 016c6930b..e2be3e019 100644
--- a/src/V3GraphAcyc.cpp
+++ b/src/V3GraphAcyc.cpp
@@ -345,7 +345,7 @@ void GraphAcyc::simplifyOut(GraphAcycVertex* avertexp) {
                 nextp = inEdgep->inNextp();
                 V3GraphVertex* inVertexp = inEdgep->fromp();
                 if (inVertexp == avertexp) {
-                    if (debug()) v3error("Non-cutable edge forms a loop, vertex=" << avertexp);
+                    if (debug()) v3error("Non-cutable vertex=" << avertexp);  // LCOV_EXCL_LINE
                     v3error("Circular logic when ordering code (non-cutable edge loop)");
                     m_origGraphp->reportLoops(
                         &V3GraphEdge::followNotCutable,
diff --git a/src/V3Hash.cpp b/src/V3Hash.cpp
index 38837e6c2..916e703a5 100644
--- a/src/V3Hash.cpp
+++ b/src/V3Hash.cpp
@@ -28,7 +28,7 @@ std::ostream& operator<<(std::ostream& os, const V3Hash& rhs) {
     return os << 'h' << std::hex << std::setw(8) << std::setfill('0') << rhs.value();
 }
 
-std::string V3Hash::toString() const {
+std::string V3Hash::toString() const VL_MT_SAFE {
     std::ostringstream os;
     os << *this;
     return os.str();
diff --git a/src/V3Hash.h b/src/V3Hash.h
index c5dd1b631..0fda1b6c9 100644
--- a/src/V3Hash.h
+++ b/src/V3Hash.h
@@ -17,6 +17,8 @@
 #ifndef VERILATOR_V3HASH_H_
 #define VERILATOR_V3HASH_H_
 
+#include "verilatedos.h"
+
 #include 
 #include 
 
@@ -45,8 +47,8 @@ public:
     explicit V3Hash(const std::string& val);
 
     // METHODS
-    uint32_t value() const { return m_value; }
-    std::string toString() const;
+    uint32_t value() const VL_MT_SAFE { return m_value; }
+    std::string toString() const VL_MT_SAFE;
 
     // OPERATORS
     // Comparisons
@@ -69,4 +71,9 @@ public:
 
 std::ostream& operator<<(std::ostream& os, const V3Hash& rhs);
 
+template <>
+struct std::hash {
+    std::size_t operator()(const V3Hash& h) const noexcept { return h.value(); }
+};
+
 #endif  // Guard
diff --git a/src/V3Hasher.cpp b/src/V3Hasher.cpp
index 97fef5716..681c68c92 100644
--- a/src/V3Hasher.cpp
+++ b/src/V3Hasher.cpp
@@ -263,6 +263,11 @@ private:
             m_hash += nodep->name();
         });
     }
+    void visit(AstCAwait* nodep) override {
+        m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, HASH_CHILDREN, [=]() {  //
+            iterateNull(nodep->sensesp());
+        });
+    }
     void visit(AstCoverInc* nodep) override {
         m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() {  //
             iterateNull(nodep->declp());
diff --git a/src/V3Inline.cpp b/src/V3Inline.cpp
index cad404fbc..3b0214adb 100644
--- a/src/V3Inline.cpp
+++ b/src/V3Inline.cpp
@@ -513,7 +513,7 @@ private:
             newmodp = nodep->modp();
         }
         // Find cell cross-references
-        nodep->modp()->foreach([](AstCell* cellp) {
+        nodep->modp()->foreach([](AstCell* cellp) {
             // clonep is nullptr when inlining the last instance, if so the use original node
             cellp->user4p(cellp->clonep() ? cellp->clonep() : cellp);
         });
diff --git a/src/V3InstrCount.cpp b/src/V3InstrCount.cpp
index 1f2cc3a9b..7aff91d73 100644
--- a/src/V3InstrCount.cpp
+++ b/src/V3InstrCount.cpp
@@ -44,6 +44,7 @@ private:
     const AstNode* const m_startNodep;  // Start node of count
     bool m_tracingCall = false;  // Iterating into a CCall to a CFunc
     bool m_inCFunc = false;  // Inside AstCFunc
+    bool m_ignoreRemaining = false;  // Ignore remaining statements in the block
     const bool m_assertNoDups;  // Check for duplicates
     const std::ostream* const m_osp;  // Dump file
 
@@ -83,7 +84,12 @@ public:
     uint32_t instrCount() const { return m_instrCount; }
 
 private:
+    void reset() {
+        m_instrCount = 0;
+        m_ignoreRemaining = false;
+    }
     uint32_t startVisitBase(AstNode* nodep) {
+        UASSERT_OBJ(!m_ignoreRemaining, nodep, "Should not reach here if ignoring");
         if (m_assertNoDups && !m_inCFunc) {
             // Ensure we don't count the same node twice
             //
@@ -112,7 +118,7 @@ private:
     void endVisitBase(uint32_t savedCount, AstNode* nodep) {
         UINFO(8, "cost " << std::setw(6) << std::left << m_instrCount << "  " << nodep << endl);
         markCost(nodep);
-        m_instrCount += savedCount;
+        if (!m_ignoreRemaining) m_instrCount += savedCount;
     }
     void markCost(AstNode* nodep) {
         if (m_osp) nodep->user4(m_instrCount + 1);  // Else don't mark to avoid writeback
@@ -120,6 +126,7 @@ private:
 
     // VISITORS
     void visit(AstNodeSel* nodep) override {
+        if (m_ignoreRemaining) return;
         // This covers both AstArraySel and AstWordSel
         //
         // If some vector is a bazillion dwords long, and we're selecting 1
@@ -131,6 +138,7 @@ private:
         iterateAndNextNull(nodep->bitp());
     }
     void visit(AstSel* nodep) override {
+        if (m_ignoreRemaining) return;
         // Similar to AstNodeSel above, a small select into a large vector
         // is not expensive. Count the cost of the AstSel itself (scales with
         // its width) and the cost of the lsbp() and widthp() nodes, but not
@@ -146,6 +154,7 @@ private:
         nodep->v3fatalSrc("AstMemberSel unhandled");
     }
     void visit(AstConcat* nodep) override {
+        if (m_ignoreRemaining) return;
         // Nop.
         //
         // Ignore concat. The problem with counting concat is that when we
@@ -166,22 +175,24 @@ private:
         markCost(nodep);
     }
     void visit(AstNodeIf* nodep) override {
+        if (m_ignoreRemaining) return;
         const VisitBase vb{this, nodep};
         iterateAndNextNull(nodep->condp());
         const uint32_t savedCount = m_instrCount;
 
-        UINFO(8, "ifsp:\n");
-        m_instrCount = 0;
+        UINFO(8, "thensp:\n");
+        reset();
         iterateAndNextNull(nodep->thensp());
         uint32_t ifCount = m_instrCount;
         if (nodep->branchPred().unlikely()) ifCount = 0;
 
         UINFO(8, "elsesp:\n");
-        m_instrCount = 0;
+        reset();
         iterateAndNextNull(nodep->elsesp());
         uint32_t elseCount = m_instrCount;
         if (nodep->branchPred().likely()) elseCount = 0;
 
+        reset();
         if (ifCount >= elseCount) {
             m_instrCount = savedCount + ifCount;
             if (nodep->elsesp()) nodep->elsesp()->user4(0);  // Don't dump it
@@ -191,6 +202,7 @@ private:
         }
     }
     void visit(AstNodeCond* nodep) override {
+        if (m_ignoreRemaining) return;
         // Just like if/else above, the ternary operator only evaluates
         // one of the two expressions, so only count the max.
         const VisitBase vb{this, nodep};
@@ -198,15 +210,16 @@ private:
         const uint32_t savedCount = m_instrCount;
 
         UINFO(8, "?\n");
-        m_instrCount = 0;
+        reset();
         iterateAndNextNull(nodep->thenp());
         const uint32_t ifCount = m_instrCount;
 
         UINFO(8, ":\n");
-        m_instrCount = 0;
+        reset();
         iterateAndNextNull(nodep->elsep());
         const uint32_t elseCount = m_instrCount;
 
+        reset();
         if (ifCount < elseCount) {
             m_instrCount = savedCount + elseCount;
             if (nodep->thenp()) nodep->thenp()->user4(0);  // Don't dump it
@@ -215,6 +228,25 @@ private:
             if (nodep->elsep()) nodep->elsep()->user4(0);  // Don't dump it
         }
     }
+    void visit(AstCAwait* nodep) override {
+        if (m_ignoreRemaining) return;
+        iterateChildren(nodep);
+        // Anything past a co_await is irrelevant
+        m_ignoreRemaining = true;
+    }
+    void visit(AstFork* nodep) override {
+        if (m_ignoreRemaining) return;
+        const VisitBase vb{this, nodep};
+        uint32_t totalCount = m_instrCount;
+        // Sum counts in each statement until the first await
+        for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
+            reset();
+            iterate(stmtp);
+            totalCount += m_instrCount;
+        }
+        m_instrCount = totalCount;
+        m_ignoreRemaining = false;
+    }
     void visit(AstActive* nodep) override {
         // You'd think that the OrderLogicVertex's would be disjoint trees
         // of stuff in the AST, but it isn't so: V3Order makes an
@@ -232,6 +264,7 @@ private:
         UASSERT_OBJ(nodep == m_startNodep, nodep, "Multiple actives, or not start node");
     }
     void visit(AstNodeCCall* nodep) override {
+        if (m_ignoreRemaining) return;
         const VisitBase vb{this, nodep};
         iterateChildren(nodep);
         m_tracingCall = true;
@@ -243,6 +276,7 @@ private:
         // from the root
         UASSERT_OBJ(m_tracingCall || nodep == m_startNodep, nodep,
                     "AstCFunc not under AstCCall, or not start node");
+        UASSERT_OBJ(!m_ignoreRemaining, nodep, "Should not be ignoring at the start of a CFunc");
         m_tracingCall = false;
         VL_RESTORER(m_inCFunc);
         {
@@ -250,8 +284,10 @@ private:
             const VisitBase vb{this, nodep};
             iterateChildren(nodep);
         }
+        m_ignoreRemaining = false;
     }
     void visit(AstNode* nodep) override {
+        if (m_ignoreRemaining) return;
         const VisitBase vb{this, nodep};
         iterateChildren(nodep);
     }
diff --git a/src/V3LangCode.h b/src/V3LangCode.h
index 9deef638f..949767489 100644
--- a/src/V3LangCode.h
+++ b/src/V3LangCode.h
@@ -48,7 +48,7 @@ public:
                                      "1800-2005", "1800-2009", "1800-2012", "1800-2017"};
         return names[m_e];
     }
-    static V3LangCode mostRecent() { return V3LangCode{L1800_2017}; }
+    static V3LangCode mostRecent() VL_MT_SAFE { return V3LangCode{L1800_2017}; }
     bool systemVerilog() const {
         return m_e == L1800_2005 || m_e == L1800_2009 || m_e == L1800_2012 || m_e == L1800_2017;
     }
diff --git a/src/V3Life.cpp b/src/V3Life.cpp
index 4a8067189..1c4bea7df 100644
--- a/src/V3Life.cpp
+++ b/src/V3Life.cpp
@@ -141,7 +141,7 @@ public:
     void checkRemoveAssign(const LifeMap::iterator& it) {
         const AstVar* const varp = it->first->varp();
         LifeVarEntry* const entp = &(it->second);
-        if (!varp->isSigPublic()) {
+        if (!varp->isSigPublic() && !varp->isUsedVirtIface()) {
             // Rather than track what sigs AstUCFunc/AstUCStmt may change,
             // we just don't optimize any public sigs
             // Check the var entry, and remove if appropriate
@@ -186,7 +186,7 @@ public:
         const auto it = m_map.find(nodep);
         if (it != m_map.end()) {
             if (AstConst* const constp = it->second.constNodep()) {
-                if (!varrefp->varp()->isSigPublic()) {
+                if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->isUsedVirtIface()) {
                     // Aha, variable is constant; substitute in.
                     // We'll later constant propagate
                     UINFO(4, "     replaceconst: " << varrefp << endl);
@@ -309,6 +309,12 @@ private:
         }
     }
     void visit(AstNodeAssign* nodep) override {
+        if (nodep->isTimingControl()) {
+            // V3Life doesn't understand time sense - don't optimize
+            setNoopt();
+            iterateChildren(nodep);
+            return;
+        }
         // Collect any used variables first, as lhs may also be on rhs
         // Similar code in V3Dead
         m_sideEffect = false;
@@ -329,7 +335,12 @@ private:
         }
     }
     void visit(AstAssignDly* nodep) override {
-        // Don't treat as normal assign; V3Life doesn't understand time sense
+        // V3Life doesn't understand time sense
+        if (nodep->isTimingControl()) {
+            // Don't optimize
+            setNoopt();
+        }
+        // Don't treat as normal assign
         iterateChildren(nodep);
     }
 
@@ -436,7 +447,13 @@ private:
     }
 
     void visit(AstVar*) override {}  // Don't want varrefs under it
-    void visit(AstNode* nodep) override { iterateChildren(nodep); }
+    void visit(AstNode* nodep) override {
+        if (nodep->isTimingControl()) {
+            // V3Life doesn't understand time sense - don't optimize
+            setNoopt();
+        }
+        iterateChildren(nodep);
+    }
 
 public:
     // CONSTRUCTORS
diff --git a/src/V3LifePost.cpp b/src/V3LifePost.cpp
index 63758d067..6ad15bad7 100644
--- a/src/V3LifePost.cpp
+++ b/src/V3LifePost.cpp
@@ -132,8 +132,8 @@ struct LifePostLocation {
 class LifePostDlyVisitor final : public VNVisitor {
 private:
     // NODE STATE
-    // Cleared on entire tree
-    //  AstVarScope::user4()    -> AstVarScope*: Passed to LifePostElim to substitute this var
+    // AstVarScope::user1()    -> bool: referenced outside _eval__nba
+    // AstVarScope::user4()    -> AstVarScope*: Passed to LifePostElim to substitute this var
     const VNUser4InUse m_inuser4;
 
     // STATE
@@ -156,6 +156,9 @@ private:
     const V3Graph* m_mtasksGraphp = nullptr;  // Mtask tracking graph
     std::unique_ptr m_checker;
 
+    const AstCFunc* const m_evalNbap;  // The _eval__nba function
+    bool m_inEvalNba = false;  // Traversing under _eval__nba
+
     // METHODS
     bool before(const LifeLocation& a, const LifeLocation& b) {
         if (a.mtaskp == b.mtaskp) return a.sequence < b.sequence;
@@ -184,8 +187,11 @@ private:
         return true;
     }
     void squashAssignposts() {
-        for (auto& itr : m_assignposts) {
-            const LifePostLocation* const app = &itr.second;
+        for (auto& pair : m_assignposts) {
+            // If referenced external to _eval__nba, don't optimize
+            if (pair.first->user1()) continue;
+
+            const LifePostLocation* const app = &pair.second;
             const AstVarRef* const lhsp = VN_AS(app->nodep->lhsp(), VarRef);  // original var
             const AstVarRef* const rhsp = VN_AS(app->nodep->rhsp(), VarRef);  // dly var
             AstVarScope* const dlyVarp = rhsp->varScopep();
@@ -273,6 +279,12 @@ private:
         LifePostElimVisitor{nodep};
     }
     void visit(AstVarRef* nodep) override {
+        // Mark variables referenced outside _eval__nba
+        if (!m_inEvalNba) {
+            nodep->varScopep()->user1(true);
+            return;
+        }
+
         // Consumption/generation of a variable,
         const AstVarScope* const vscp = nodep->varScopep();
         UASSERT_OBJ(vscp, nodep, "Scope not assigned");
@@ -287,6 +299,7 @@ private:
         // The pre-assignment into the dly var should not count as its
         // first write; we only want to consider reads and writes that
         // would still happen if the dly var were eliminated.
+        if (!m_inEvalNba) iterateChildren(nodep);
     }
     void visit(AstAssignPost* nodep) override {
         // Don't record ASSIGNPOST in the read/write maps, record them in a
@@ -314,9 +327,11 @@ private:
     }
     void visit(AstExecGraph* nodep) override {
         // Treat the ExecGraph like a call to each mtask body
-        UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
-        m_mtasksGraphp = nodep->depGraphp();
-        for (V3GraphVertex* mtaskVxp = m_mtasksGraphp->verticesBeginp(); mtaskVxp;
+        if (m_inEvalNba) {
+            UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
+            m_mtasksGraphp = nodep->depGraphp();
+        }
+        for (V3GraphVertex* mtaskVxp = nodep->depGraphp()->verticesBeginp(); mtaskVxp;
              mtaskVxp = mtaskVxp->verticesNextp()) {
             const ExecMTask* const mtaskp = dynamic_cast(mtaskVxp);
             m_execMTaskp = mtaskp;
@@ -327,6 +342,8 @@ private:
     }
     void visit(AstCFunc* nodep) override {
         if (!m_tracingCall && !nodep->entryPoint()) return;
+        VL_RESTORER(m_inEvalNba);
+        if (nodep == m_evalNbap) m_inEvalNba = true;
         m_tracingCall = false;
         iterateChildren(nodep);
     }
@@ -336,7 +353,10 @@ private:
 
 public:
     // CONSTRUCTORS
-    explicit LifePostDlyVisitor(AstNetlist* nodep) { iterate(nodep); }
+    explicit LifePostDlyVisitor(AstNetlist* netlistp)
+        : m_evalNbap{netlistp->evalNbap()} {
+        iterate(netlistp);
+    }
     ~LifePostDlyVisitor() override {
         V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel);
     }
diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp
index c92a41a10..e22497eb9 100644
--- a/src/V3LinkCells.cpp
+++ b/src/V3LinkCells.cpp
@@ -355,7 +355,7 @@ private:
         for (AstPin *nextp, *pinp = nodep->pinsp(); pinp; pinp = nextp) {
             nextp = VN_AS(pinp->nextp(), Pin);
             if (pinp->dotStar()) {
-                if (pinStar) pinp->v3error("Duplicate .* in an instance");
+                if (pinStar) pinp->v3error("Duplicate .* in an instance (IEEE 1800-2017 23.3.2)");
                 pinStar = true;
                 // Done with this fake pin
                 VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp);
@@ -374,8 +374,10 @@ private:
             // Note what pins exist
             std::unordered_set ports;  // Symbol table of all connected port names
             for (AstPin* pinp = nodep->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
-                if (pinp->name() == "")
-                    pinp->v3error("Connect by position is illegal in .* connected instances");
+                if (pinStar && pinp->name().substr(0, 11) == "__pinNumber") {
+                    pinp->v3error("Connect by position is illegal in .* connected instances"
+                                  " (IEEE 1800-2017 23.3.2)");
+                }
                 if (!pinp->exprp()) {
                     if (pinp->name().substr(0, 11) == "__pinNumber") {
                         pinp->v3warn(PINNOCONNECT,
diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp
index 75681218f..3ed4bd599 100644
--- a/src/V3LinkDot.cpp
+++ b/src/V3LinkDot.cpp
@@ -161,6 +161,7 @@ private:
     bool m_forPrimary;  // First link
     bool m_forPrearray;  // Compress cell__[array] refs
     bool m_forScopeCreation;  // Remove VarXRefs for V3Scope
+    bool m_removeVoidParamedClasses;  // Remove classes with void params
 
 public:
     // METHODS
@@ -207,6 +208,7 @@ public:
         m_forPrimary = (step == LDS_PRIMARY);
         m_forPrearray = (step == LDS_PARAMED || step == LDS_PRIMARY);
         m_forScopeCreation = (step == LDS_SCOPED);
+        m_removeVoidParamedClasses = (step == LDS_PARAMED);
         s_errorThisp = this;
         V3Error::errorExitCb(preErrorDumpHandler);  // If get error, dump self
     }
@@ -220,6 +222,7 @@ public:
     bool forPrimary() const { return m_forPrimary; }
     bool forPrearray() const { return m_forPrearray; }
     bool forScopeCreation() const { return m_forScopeCreation; }
+    bool removeVoidParamedClasses() const { return m_removeVoidParamedClasses; }
 
     // METHODS
     static string nodeTextType(AstNode* nodep) {
@@ -308,6 +311,19 @@ public:
         if (forScopeCreation()) m_nameScopeSymMap.emplace(scopename, symp);
         return symp;
     }
+    VSymEnt* insertTopIface(AstCell* nodep, const string& scopename) {
+        VSymEnt* const symp = new VSymEnt{&m_syms, nodep};
+        UINFO(9, "      INSERTtopiface se" << cvtToHex(symp) << "  " << scopename << " " << nodep
+                                           << endl);
+        symp->parentp(rootEntp());  // Needed so backward search can find name of top module
+        symp->fallbackp(dunitEntp());  // Needed so can find $unit stuff
+        nodep->user1p(symp);
+        if (nodep->modp()) nodep->modp()->user1p(symp);
+        checkDuplicate(rootEntp(), nodep, nodep->origName());
+        rootEntp()->insert(nodep->origName(), symp);
+        if (forScopeCreation()) m_nameScopeSymMap.emplace(scopename, symp);
+        return symp;
+    }
     VSymEnt* insertCell(VSymEnt* abovep, VSymEnt* modSymp, AstCell* nodep,
                         const string& scopename) {
         UASSERT_OBJ(abovep, nodep, "Null symbol table inserting node");
@@ -443,7 +459,7 @@ public:
     }
     void computeIfaceVarSyms() {
         for (VSymEnt* varSymp : m_ifaceVarSyms) {
-            const AstVar* const varp = varSymp ? VN_AS(varSymp->nodep(), Var) : nullptr;
+            AstVar* const varp = varSymp ? VN_AS(varSymp->nodep(), Var) : nullptr;
             UINFO(9, "  insAllIface se" << cvtToHex(varSymp) << " " << varp << endl);
             AstIfaceRefDType* const ifacerefp = ifaceRefFromArray(varp->subDTypep());
             UASSERT_OBJ(ifacerefp, varp, "Non-ifacerefs on list!");
@@ -459,8 +475,16 @@ public:
                     ifacerefp->v3fatalSrc("Unlinked interface");
                 }
             } else if (ifacerefp->ifaceViaCellp()->dead()) {
-                ifacerefp->v3error("Parent instance's interface is not found: "
-                                   << AstNode::prettyNameQ(ifacerefp->ifaceName()));
+                if (varp->isIfaceRef()) {
+                    ifacerefp->v3error("Parent instance's interface is not found: "
+                                       << AstNode::prettyNameQ(ifacerefp->ifaceName()));
+                } else {
+                    ifacerefp->v3warn(
+                        E_UNSUPPORTED,
+                        "Unsupported: virtual interface never assigned any actual interface");
+                    varp->dtypep(ifacerefp->findCHandleDType());
+                    VL_DO_DANGLING(ifacerefp->unlinkFrBack()->deleteTree(), ifacerefp);
+                }
                 continue;
             }
             VSymEnt* const ifaceSymp = getNodeSym(ifacerefp->ifaceViaCellp());
@@ -764,8 +788,55 @@ class LinkDotFindVisitor final : public VNVisitor {
              modp = VN_AS(modp->nextp(), NodeModule)) {
             UINFO(8, "Top Module: " << modp << endl);
             m_scope = "TOP";
+
+            if (m_statep->forPrearray() && v3Global.opt.topIfacesSupported()) {
+                for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) {
+                    if (AstVar* const varp = VN_CAST(subnodep, Var)) {
+                        if (varp->isIfaceRef()) {
+                            const AstNodeDType* const subtypep = varp->subDTypep();
+                            const AstIfaceRefDType* ifacerefp = nullptr;
+                            if (VN_IS(subtypep, IfaceRefDType)) {
+                                ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType);
+                            } else if (VN_IS(subtypep, BracketArrayDType)) {
+                                const AstBracketArrayDType* const arrp
+                                    = VN_AS(subtypep, BracketArrayDType);
+                                const AstNodeDType* const arrsubtypep = arrp->subDTypep();
+                                if (VN_IS(arrsubtypep, IfaceRefDType)) {
+                                    ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
+                                }
+                            } else if (VN_IS(subtypep, UnpackArrayDType)) {
+                                const AstUnpackArrayDType* const arrp
+                                    = VN_AS(subtypep, UnpackArrayDType);
+                                const AstNodeDType* const arrsubtypep = arrp->subDTypep();
+                                if (VN_IS(arrsubtypep, IfaceRefDType)) {
+                                    ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
+                                }
+                            }
+
+                            if (ifacerefp && !ifacerefp->cellp()) {
+                                // A dummy cell to keep the top level interface alive and correctly
+                                // optimized for default parameter values
+                                AstCell* ifacecellp
+                                    = new AstCell{nodep->fileline(),
+                                                  nodep->fileline(),
+                                                  modp->name() + "__02E" + varp->name(),
+                                                  ifacerefp->ifaceName(),
+                                                  nullptr,
+                                                  nullptr,
+                                                  nullptr};
+                                ifacecellp->modp(ifacerefp->ifacep());
+                                m_curSymp = m_modSymp
+                                    = m_statep->insertTopIface(ifacecellp, m_scope);
+                                { iterate(ifacecellp); }
+                            }
+                        }
+                    }
+                }
+            }
+
             m_curSymp = m_modSymp = m_statep->insertTopCell(modp, m_scope);
             { iterate(modp); }
+
             m_scope = "";
             m_curSymp = m_modSymp = nullptr;
         }
@@ -842,6 +913,18 @@ class LinkDotFindVisitor final : public VNVisitor {
     void visit(AstClass* nodep) override {
         UASSERT_OBJ(m_curSymp, nodep, "Class not under module/package/$unit");
         UINFO(8, "   " << nodep << endl);
+        // Remove classes that have void params, as they were only used for the parametrization
+        // step and will not be instantiated
+        if (m_statep->removeVoidParamedClasses()) {
+            for (auto* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
+                if (auto* dtypep = VN_CAST(stmtp, ParamTypeDType)) {
+                    if (VN_IS(dtypep->subDTypep(), VoidDType)) {
+                        VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
+                        return;
+                    }
+                }
+            }
+        }
         VL_RESTORER(m_scope);
         VL_RESTORER(m_classOrPackagep);
         VL_RESTORER(m_modSymp);
@@ -1986,14 +2069,23 @@ private:
     }
 
     bool isParamedClassRef(const AstNode* nodep) {
+        // Is this a parametrized reference to a class, or a reference to class parameter
         if (const auto* classRefp = VN_CAST(nodep, ClassOrPackageRef)) {
             if (classRefp->paramsp()) return true;
             const auto* classp = classRefp->classOrPackageNodep();
             while (const auto* typedefp = VN_CAST(classp, Typedef)) classp = typedefp->subDTypep();
-            return VN_IS(classp, ClassRefDType) && VN_AS(classp, ClassRefDType)->paramsp();
+            return (VN_IS(classp, ClassRefDType) && VN_AS(classp, ClassRefDType)->paramsp())
+                   || VN_IS(classp, ParamTypeDType);
         }
         return false;
     }
+    VSymEnt* getThisClassSymp() {
+        VSymEnt* classSymp = m_ds.m_dotSymp;
+        do {
+            classSymp = classSymp->parentp();
+        } while (classSymp && !VN_IS(classSymp->nodep(), Class));
+        return classSymp;
+    }
 
     // VISITs
     void visit(AstNetlist* nodep) override {
@@ -2141,10 +2233,7 @@ private:
             m_ds.m_dotPos = DP_SCOPE;
 
             if (VN_IS(nodep->lhsp(), ParseRef) && nodep->lhsp()->name() == "this") {
-                VSymEnt* classSymp = m_ds.m_dotSymp;
-                do {
-                    classSymp = classSymp->parentp();
-                } while (classSymp && !VN_IS(classSymp->nodep(), Class));
+                VSymEnt* classSymp = getThisClassSymp();
                 if (!classSymp) {
                     nodep->v3error("'this' used outside class (IEEE 1800-2017 8.11)");
                     m_ds.m_dotErr = true;
@@ -2153,10 +2242,7 @@ private:
                     UINFO(8, "     this. " << m_ds.ascii() << endl);
                 }
             } else if (VN_IS(nodep->lhsp(), ParseRef) && nodep->lhsp()->name() == "super") {
-                const VSymEnt* classSymp = m_ds.m_dotSymp;
-                do {
-                    classSymp = classSymp->parentp();
-                } while (classSymp && !VN_IS(classSymp->nodep(), Class));
+                const VSymEnt* classSymp = getThisClassSymp();
                 if (!classSymp) {
                     nodep->v3error("'super' used outside class (IEEE 1800-2017 8.15)");
                     m_ds.m_dotErr = true;
@@ -2250,6 +2336,22 @@ private:
             nodep->v3warn(E_UNSUPPORTED, "Unsupported: super");
             m_ds.m_dotErr = true;
         }
+        if (nodep->name() == "this") {
+            iterateChildren(nodep);
+            if (m_statep->forPrimary()) return;  // The class might be parametrized somewhere
+            const VSymEnt* classSymp = getThisClassSymp();
+            if (!classSymp) {
+                nodep->v3error("'this' used outside class (IEEE 1800-2017 8.11)");
+                return;
+            }
+            AstClass* const classp = VN_AS(classSymp->nodep(), Class);
+            AstClassRefDType* const dtypep
+                = new AstClassRefDType{nodep->fileline(), classp, nullptr};
+            AstThisRef* const newp = new AstThisRef{nodep->fileline(), dtypep};
+            nodep->replaceWith(newp);
+            VL_DO_DANGLING(pushDeletep(nodep), nodep);
+            return;
+        }
         if (m_ds.m_dotPos == DP_FINAL && VN_IS(m_ds.m_unlinkedScopep, LambdaArgRef)
             && nodep->name() == "index") {
             // 'with' statement's 'item.index'
@@ -2328,7 +2430,7 @@ private:
                     m_ds.m_dotSymp = foundp;
                     m_ds.m_dotPos = DP_SCOPE;
                     // Upper AstDot visitor will handle it from here
-                } else if (VN_IS(foundp->nodep(), Cell) && allowVar && m_cellp) {
+                } else if (VN_IS(foundp->nodep(), Cell) && allowVar) {
                     AstCell* const cellp = VN_AS(foundp->nodep(), Cell);
                     if (VN_IS(cellp->modp(), Iface)) {
                         // Interfaces can be referenced like a variable for interconnect
@@ -2346,7 +2448,7 @@ private:
                         m_ds.m_dotPos = DP_SCOPE;
                         UINFO(9, " cell -> iface varref " << foundp->nodep() << endl);
                         AstNode* const newp
-                            = new AstVarRef(ifaceRefVarp->fileline(), ifaceRefVarp, VAccess::READ);
+                            = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ};
                         nodep->replaceWith(newp);
                         VL_DO_DANGLING(pushDeletep(nodep), nodep);
                     } else if (VN_IS(cellp->modp(), NotFoundModule)) {
@@ -2357,7 +2459,7 @@ private:
             } else if (AstVar* const varp = foundToVarp(foundp, nodep, VAccess::READ)) {
                 AstIfaceRefDType* const ifacerefp
                     = LinkDotState::ifaceRefFromArray(varp->subDTypep());
-                if (ifacerefp) {
+                if (ifacerefp && varp->isIfaceRef()) {
                     UASSERT_OBJ(ifacerefp->ifaceViaCellp(), ifacerefp, "Unlinked interface");
                     // Really this is a scope reference into an interface
                     UINFO(9, "varref-ifaceref " << m_ds.m_dotText << "  " << nodep << endl);
@@ -2446,9 +2548,9 @@ private:
                     m_ds.m_dotText = VString::dot(m_ds.m_dotText, ".", nodep->name());
                     m_ds.m_dotSymp = foundp;
                     m_ds.m_dotPos = DP_SCOPE;
-                    UINFO(9, " cell -> iface varref " << foundp->nodep() << endl);
-                    AstNode* newp
-                        = new AstVarRef(ifaceRefVarp->fileline(), ifaceRefVarp, VAccess::READ);
+                    UINFO(9, " modport -> iface varref " << foundp->nodep() << endl);
+                    // We lose the modport name here, so we cannot detect mismatched modports.
+                    AstNode* newp = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ};
                     auto* const cellarrayrefp = VN_CAST(m_ds.m_unlinkedScopep, CellArrayRef);
                     if (cellarrayrefp) {
                         // iface[vec].modport became CellArrayRef(iface, lsb)
@@ -2533,8 +2635,9 @@ private:
         // Class: Recurse inside or cleanup not founds
         // checkNoDot not appropriate, can be under a dot
         AstNode::user5ClearTree();
-        UASSERT_OBJ(m_statep->forPrimary() || nodep->classOrPackagep(), nodep,
-                    "ClassRef has unlinked class");
+        UASSERT_OBJ(m_statep->forPrimary() || VN_IS(nodep->classOrPackageNodep(), ParamTypeDType)
+                        || nodep->classOrPackagep(),
+                    nodep, "ClassRef has unlinked class");
         UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp(), nodep,
                     "class reference parameter not removed by V3Param");
         VL_RESTORER(m_ds);
diff --git a/src/V3LinkInc.cpp b/src/V3LinkInc.cpp
index 91bfbe530..7c12c4e28 100644
--- a/src/V3LinkInc.cpp
+++ b/src/V3LinkInc.cpp
@@ -60,6 +60,7 @@ private:
     };
 
     // STATE
+    AstNodeFTask* m_ftaskp = nullptr;  // Function or task we're inside
     int m_modIncrementsNum = 0;  // Var name counter
     InsertMode m_insMode = IM_BEFORE;  // How to insert
     AstNode* m_insStmtp = nullptr;  // Where to insert statement
@@ -94,6 +95,11 @@ private:
         m_modIncrementsNum = 0;
         iterateChildren(nodep);
     }
+    void visit(AstNodeFTask* nodep) override {
+        VL_RESTORER(m_ftaskp);
+        m_ftaskp = nodep;
+        iterateChildren(nodep);
+    }
     void visit(AstWhile* nodep) override {
         // Special, as statements need to be put in different places
         // Preconditions insert first just before themselves (the normal
@@ -149,6 +155,25 @@ private:
         nodep->v3fatalSrc(
             "For statements should have been converted to while statements in V3Begin.cpp");
     }
+    void visit(AstDelay* nodep) override {
+        m_insStmtp = nodep;
+        iterateAndNextNull(nodep->lhsp());
+        m_insStmtp = nullptr;
+        iterateAndNextNull(nodep->stmtsp());
+        m_insStmtp = nullptr;
+    }
+    void visit(AstEventControl* nodep) override {
+        m_insStmtp = nullptr;
+        iterateAndNextNull(nodep->stmtsp());
+        m_insStmtp = nullptr;
+    }
+    void visit(AstWait* nodep) override {
+        m_insStmtp = nodep;
+        iterateAndNextNull(nodep->condp());
+        m_insStmtp = nullptr;
+        iterateAndNextNull(nodep->stmtsp());
+        m_insStmtp = nullptr;
+    }
     void visit(AstNodeStmt* nodep) override {
         if (!nodep->isStatement()) {
             iterateChildren(nodep);
@@ -170,6 +195,7 @@ private:
     void visit(AstLogEq* nodep) override { unsupported_visit(nodep); }
     void visit(AstLogIf* nodep) override { unsupported_visit(nodep); }
     void visit(AstNodeCond* nodep) override { unsupported_visit(nodep); }
+    void visit(AstPropClocked* nodep) override { unsupported_visit(nodep); }
     void prepost_visit(AstNodeTriop* nodep) {
         // Check if we are underneath a statement
         if (!m_insStmtp) {
@@ -221,6 +247,7 @@ private:
         const string name = string("__Vincrement") + cvtToStr(++m_modIncrementsNum);
         AstVar* const varp = new AstVar(fl, VVarType::BLOCKTEMP, name, VFlagChildDType(),
                                         varrefp->varp()->subDTypep()->cloneTree(true));
+        if (m_ftaskp) varp->funcLocal(true);
 
         // Declare the variable
         insertBeforeStmt(nodep, varp);
@@ -254,7 +281,7 @@ private:
         }
 
         // Replace the node with the temporary
-        nodep->replaceWith(new AstVarRef(varrefp->fileline(), varp, VAccess::WRITE));
+        nodep->replaceWith(new AstVarRef{varrefp->fileline(), varp, VAccess::READ});
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
     void visit(AstPreAdd* nodep) override { prepost_visit(nodep); }
diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp
index 5243ed1cb..e42e37987 100644
--- a/src/V3LinkJump.cpp
+++ b/src/V3LinkJump.cpp
@@ -175,17 +175,6 @@ private:
         nodep->replaceWith(newp);
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
-    void visit(AstWait* nodep) override {
-        nodep->v3warn(E_UNSUPPORTED, "Unsupported: wait statements");
-        // Statements we'll just execute immediately; equivalent to if they followed this
-        if (AstNode* const bodysp = nodep->stmtsp()) {
-            bodysp->unlinkFrBackWithNext();
-            nodep->replaceWith(bodysp);
-        } else {
-            nodep->unlinkFrBack();
-        }
-        VL_DO_DANGLING(nodep->deleteTree(), nodep);
-    }
     void visit(AstWhile* nodep) override {
         // Don't need to track AstRepeat/AstFor as they have already been converted
         VL_RESTORER(m_loopp);
diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp
index d0e01d997..903f0a90c 100644
--- a/src/V3LinkLValue.cpp
+++ b/src/V3LinkLValue.cpp
@@ -100,6 +100,13 @@ private:
             iterateAndNextNull(nodep->lhsp());
         }
     }
+    void visit(AstFireEvent* nodep) override {
+        VL_RESTORER(m_setRefLvalue);
+        {
+            m_setRefLvalue = VAccess::WRITE;
+            iterateAndNextNull(nodep->operandp());
+        }
+    }
     void visit(AstCastDynamic* nodep) override {
         VL_RESTORER(m_setRefLvalue);
         {
@@ -294,9 +301,9 @@ private:
         }
     }
     void visit(AstNodeFTask* nodep) override {
+        VL_RESTORER(m_ftaskp);
         m_ftaskp = nodep;
         iterateChildren(nodep);
-        m_ftaskp = nullptr;
     }
     void visit(AstNodeFTaskRef* nodep) override {
         AstNode* pinp = nodep->pinsp();
diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp
index cdb9b19c8..417cd08dc 100644
--- a/src/V3LinkLevel.cpp
+++ b/src/V3LinkLevel.cpp
@@ -199,6 +199,36 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) {
                     } else {
                         ioNames.insert(oldvarp->name());
                     }
+                } else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) {
+                    const AstNodeDType* const subtypep = oldvarp->subDTypep();
+                    if (VN_IS(subtypep, IfaceRefDType)) {
+                        const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType);
+                        if (!ifacerefp->cellp()) {
+                            if (ioNames.find(oldvarp->name()) != ioNames.end()) {
+                                // UINFO(8, "Multitop dup interface found: " << oldvarp << endl);
+                                dupNames.insert(oldvarp->name());
+                            } else {
+                                ioNames.insert(oldvarp->name());
+                            }
+                        }
+                    }
+                    if (VN_IS(subtypep, UnpackArrayDType)) {
+                        const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType);
+                        const AstNodeDType* const arrsubtypep = arrp->subDTypep();
+                        if (VN_IS(arrsubtypep, IfaceRefDType)) {
+                            const AstIfaceRefDType* const ifacerefp
+                                = VN_AS(arrsubtypep, IfaceRefDType);
+                            if (!ifacerefp->cellp()) {
+                                if (ioNames.find(oldvarp->name()) != ioNames.end()) {
+                                    // UINFO(8, "Multitop dup interface array found: " << oldvarp
+                                    // << endl);
+                                    dupNames.insert(oldvarp->name());
+                                } else {
+                                    ioNames.insert(oldvarp->name());
+                                }
+                            }
+                        }
+                    }
                 }
             }
         }
@@ -256,6 +286,104 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) {
                     // Skip length and width comp; we know it's a direct assignment
                     pinp->modVarp(oldvarp);
                     cellp->addPinsp(pinp);
+                } else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) {
+                    // for each interface port on oldmodp instantiate a corresponding interface
+                    // cell in $root
+                    const AstNodeDType* const subtypep = oldvarp->subDTypep();
+                    if (VN_IS(subtypep, IfaceRefDType)) {
+                        const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType);
+                        if (!ifacerefp->cellp()) {
+                            string name = oldvarp->name();
+                            if (dupNames.find(name) != dupNames.end()) {
+                                // __02E=. while __DOT__ looks nicer but will break V3LinkDot
+                                name = oldmodp->name() + "__02E" + name;
+                            }
+
+                            AstCell* ifacecellp = new AstCell{newmodp->fileline(),
+                                                              newmodp->fileline(),
+                                                              name,
+                                                              ifacerefp->ifaceName(),
+                                                              nullptr,
+                                                              nullptr,
+                                                              nullptr};
+                            ifacecellp->modp(ifacerefp->ifacep());
+                            newmodp->addStmtsp(ifacecellp);
+
+                            AstIfaceRefDType* const idtypep = new AstIfaceRefDType{
+                                newmodp->fileline(), name, ifacerefp->ifaceName()};
+                            idtypep->ifacep(nullptr);
+                            idtypep->dtypep(idtypep);
+                            idtypep->cellp(ifacecellp);
+                            rootp->typeTablep()->addTypesp(idtypep);
+
+                            AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF,
+                                                      name + "__Viftop", idtypep};
+                            varp->isIfaceParent(true);
+                            ifacecellp->addNextHere(varp);
+                            ifacecellp->hasIfaceVar(true);
+
+                            AstPin* const pinp
+                                = new AstPin{oldvarp->fileline(), 0, varp->name(),
+                                             new AstVarRef{varp->fileline(), varp,
+                                                           oldvarp->isWritable() ? VAccess::WRITE
+                                                                                 : VAccess::READ}};
+                            pinp->modVarp(oldvarp);
+                            cellp->addPinsp(pinp);
+                        }
+                    } else if (VN_IS(subtypep, UnpackArrayDType)) {
+                        const AstUnpackArrayDType* const oldarrp
+                            = VN_AS(subtypep, UnpackArrayDType);
+                        const AstNodeDType* const arrsubtypep = oldarrp->subDTypep();
+                        if (VN_IS(arrsubtypep, IfaceRefDType)) {
+                            const AstIfaceRefDType* const ifacerefp
+                                = VN_AS(arrsubtypep, IfaceRefDType);
+                            if (!ifacerefp->cellp()) {
+                                string name = oldvarp->name();
+                                if (dupNames.find(name) != dupNames.end()) {
+                                    // __02E=. while __DOT__ looks nicer but will break V3LinkDot
+                                    name = oldmodp->name() + "__02E" + name;
+                                }
+
+                                AstUnpackArrayDType* arraydtypep
+                                    = VN_AS(oldvarp->dtypep(), UnpackArrayDType);
+                                AstCell* ifacearraycellp
+                                    = new AstCell{newmodp->fileline(),
+                                                  newmodp->fileline(),
+                                                  name,
+                                                  ifacerefp->ifaceName(),
+                                                  nullptr,
+                                                  nullptr,
+                                                  arraydtypep->rangep()->cloneTree(true)};
+                                ifacearraycellp->modp(ifacerefp->ifacep());
+                                newmodp->addStmtsp(ifacearraycellp);
+
+                                AstIfaceRefDType* const idtypep = new AstIfaceRefDType{
+                                    newmodp->fileline(), name, ifacerefp->ifaceName()};
+                                idtypep->ifacep(nullptr);
+                                idtypep->dtypep(idtypep);
+                                idtypep->cellp(ifacearraycellp);
+                                rootp->typeTablep()->addTypesp(idtypep);
+
+                                AstNodeArrayDType* const arrp = new AstUnpackArrayDType{
+                                    newmodp->fileline(), idtypep,
+                                    arraydtypep->rangep()->cloneTree(true)};
+                                AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF,
+                                                          name + "__Viftop", arrp};
+                                varp->isIfaceParent(true);
+                                ifacearraycellp->addNextHere(varp);
+                                ifacearraycellp->hasIfaceVar(true);
+                                rootp->typeTablep()->addTypesp(arrp);
+
+                                AstPin* const pinp = new AstPin{
+                                    oldvarp->fileline(), 0, varp->name(),
+                                    new AstVarRef{varp->fileline(), varp,
+                                                  oldvarp->isWritable() ? VAccess::WRITE
+                                                                        : VAccess::READ}};
+                                pinp->modVarp(oldvarp);
+                                cellp->addPinsp(pinp);
+                            }
+                        }
+                    }
                 }
             }
         }
diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp
index 43ade69e9..7dd81fb2a 100644
--- a/src/V3LinkParse.cpp
+++ b/src/V3LinkParse.cpp
@@ -209,18 +209,16 @@ private:
         }
         if (VN_IS(nodep->subDTypep(), ParseTypeDType)) {
             // It's a parameter type. Use a different node type for this.
-            AstNodeDType* const dtypep = VN_CAST(nodep->valuep(), NodeDType);
-            if (!dtypep) {
-                nodep->v3error(
-                    "Parameter type's initial value isn't a type: " << nodep->prettyNameQ());
-                nodep->unlinkFrBack();
-            } else {
+            AstNodeDType* dtypep = VN_CAST(nodep->valuep(), NodeDType);
+            if (dtypep) {
                 dtypep->unlinkFrBack();
-                AstNode* const newp = new AstParamTypeDType(
-                    nodep->fileline(), nodep->varType(), nodep->name(), VFlagChildDType(), dtypep);
-                nodep->replaceWith(newp);
-                VL_DO_DANGLING(nodep->deleteTree(), nodep);
+            } else {
+                dtypep = new AstVoidDType{nodep->fileline()};
             }
+            AstNode* const newp = new AstParamTypeDType{nodep->fileline(), nodep->varType(),
+                                                        nodep->name(), VFlagChildDType{}, dtypep};
+            nodep->replaceWith(newp);
+            VL_DO_DANGLING(nodep->deleteTree(), nodep);
             return;
         }
 
@@ -281,7 +279,7 @@ private:
                                                  nodep->valuep()->unlinkFrBack()));
             }
         }
-        if (nodep->isIfaceRef() && !nodep->isIfaceParent()) {
+        if (nodep->isIfaceRef() && !nodep->isIfaceParent() && !v3Global.opt.topIfacesSupported()) {
             // Only AstIfaceRefDType's at this point correspond to ports;
             // haven't made additional ones for interconnect yet, so assert is simple
             // What breaks later is we don't have a Scope/Cell representing
@@ -302,7 +300,7 @@ private:
             VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
         } else if (nodep->attrType() == VAttrType::VAR_CLOCK_ENABLE) {
             UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
-            m_varp->attrClockEn(true);
+            // Accepted and silently ignored for backward compatibility, but has no effect
             VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
         } else if (nodep->attrType() == VAttrType::VAR_FORCEABLE) {
             UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
@@ -589,9 +587,7 @@ private:
                              << nodep->warnMore() << "... Suggest use a normal 'always'");
             VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
         } else if (alwaysp && !alwaysp->sensesp()) {
-            // Verilator is still ony supporting SenTrees under an always,
-            // so allow the parser to handle everything and shim to
-            // historical AST here
+            // If the event control is at the top, move the sentree to the always
             if (AstSenTree* const sensesp = nodep->sensesp()) {
                 sensesp->unlinkFrBackWithNext();
                 alwaysp->sensesp(sensesp);
diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp
index 9e314992a..03cb0abf0 100644
--- a/src/V3LinkResolve.cpp
+++ b/src/V3LinkResolve.cpp
@@ -144,75 +144,6 @@ private:
             nodep->scopeNamep(new AstScopeName{nodep->fileline(), false});
         }
     }
-
-    void visit(AstSenItem* nodep) override {
-        // Remove bit selects, and bark if it's not a simple variable
-        iterateChildren(nodep);
-        if (nodep->isClocked()) {
-            // If it's not a simple variable wrap in a temporary
-            // This is a bit unfortunate as we haven't done width resolution
-            // and any width errors will look a bit odd, but it works.
-            AstNode* const sensp = nodep->sensp();
-            if (sensp && !VN_IS(sensp, NodeVarRef) && !VN_IS(sensp, Const)) {
-                // Make a new temp wire
-                const string newvarname = "__Vsenitemexpr" + cvtToStr(++m_senitemCvtNum);
-                AstVar* const newvarp = new AstVar(sensp->fileline(), VVarType::MODULETEMP,
-                                                   newvarname, VFlagLogicPacked(), 1);
-                // We can't just add under the module, because we may be
-                // inside a generate, begin, etc.
-                // We know a SenItem should be under a SenTree/Always etc,
-                // we we'll just hunt upwards
-                AstNode* addwherep = nodep;  // Add to this element's next
-                while (VN_IS(addwherep, SenItem) || VN_IS(addwherep, SenTree)) {
-                    addwherep = addwherep->backp();
-                }
-                if (!VN_IS(addwherep, Always)) {  // Assertion perhaps?
-                    sensp->v3warn(E_UNSUPPORTED,
-                                  "Unsupported: Non-single-bit pos/negedge clock statement under "
-                                  "some complicated block");
-                    addwherep = m_modp;
-                }
-                addwherep->addNext(newvarp);
-
-                sensp->replaceWith(new AstVarRef(sensp->fileline(), newvarp, VAccess::READ));
-                AstAssignW* const assignp = new AstAssignW(
-                    sensp->fileline(), new AstVarRef(sensp->fileline(), newvarp, VAccess::WRITE),
-                    sensp);
-                addwherep->addNext(assignp);
-            }
-        } else {  // Old V1995 sensitivity list; we'll probably mostly ignore
-            bool did = true;
-            while (did) {
-                did = false;
-                if (AstNodeSel* const selp = VN_CAST(nodep->sensp(), NodeSel)) {
-                    AstNode* const fromp = selp->fromp()->unlinkFrBack();
-                    selp->replaceWith(fromp);
-                    VL_DO_DANGLING(selp->deleteTree(), selp);
-                    did = true;
-                }
-                // NodeSel doesn't include AstSel....
-                if (AstSel* const selp = VN_CAST(nodep->sensp(), Sel)) {
-                    AstNode* const fromp = selp->fromp()->unlinkFrBack();
-                    selp->replaceWith(fromp);
-                    VL_DO_DANGLING(selp->deleteTree(), selp);
-                    did = true;
-                }
-                if (AstNodePreSel* const selp = VN_CAST(nodep->sensp(), NodePreSel)) {
-                    AstNode* const fromp = selp->fromp()->unlinkFrBack();
-                    selp->replaceWith(fromp);
-                    VL_DO_DANGLING(selp->deleteTree(), selp);
-                    did = true;
-                }
-            }
-        }
-        if (!VN_IS(nodep->sensp(), NodeVarRef)
-            && !VN_IS(nodep->sensp(), EnumItemRef)  // V3Const will cleanup
-            && !nodep->isIllegal()) {
-            if (debug()) nodep->dumpTree(cout, "-tree: ");
-            nodep->v3warn(E_UNSUPPORTED, "Unsupported: Complex statement in sensitivity list");
-        }
-    }
-
     void visit(AstNodePreSel* nodep) override {
         if (!nodep->attrp()) {
             iterateChildren(nodep);
diff --git a/src/V3List.h b/src/V3List.h
index 883db89af..e7e524c6e 100644
--- a/src/V3List.h
+++ b/src/V3List.h
@@ -44,6 +44,8 @@ public:
     // METHODS
     T begin() const { return m_headp; }
     T end() const { return nullptr; }
+    T rbegin() const { return m_tailp; }
+    T rend() const { return nullptr; }
     bool empty() const { return m_headp == nullptr; }
     void reset() {  // clear() without walking the list
         m_headp = nullptr;
@@ -78,6 +80,7 @@ public:
 #endif
     }
     T nextp() const { return m_nextp; }
+    T prevp() const { return m_prevp; }
     // METHODS
     void pushBack(V3List& listr, T newp) {
         // "this" must be a element inside of *newp
@@ -116,6 +119,23 @@ public:
         }
         m_prevp = m_nextp = nullptr;
     }
+    // Remove all nodes from 'oldListr', append them to 'newListr'. 'this' must be a member of the
+    // object at 'selfp', and 'selfp' must be the head of the list in 'oldListr'.
+    void moveAppend(V3List& oldListr, V3List& newListr, T selfp) {
+        UASSERT(selfp == oldListr.m_headp, "Must be head of list to use 'moveAppend'");
+        const size_t offset = (size_t)(uint8_t*)(this) - (size_t)(uint8_t*)(selfp);
+        const T headp = selfp;
+        const T tailp = oldListr.m_tailp;
+        oldListr.reset();
+        if (newListr.empty()) {
+            newListr.m_headp = headp;
+            newListr.m_tailp = tailp;
+        } else {
+            baseToListEnt(newListr.m_tailp, offset)->m_nextp = headp;
+            m_prevp = newListr.m_tailp;
+            newListr.m_tailp = tailp;
+        }
+    }
 };
 
 //============================================================================
diff --git a/src/V3Localize.cpp b/src/V3Localize.cpp
index 0d5977dcc..1de5d06e3 100644
--- a/src/V3Localize.cpp
+++ b/src/V3Localize.cpp
@@ -169,6 +169,7 @@ private:
             && !nodep->varp()->isFuncLocal()  // Not already a function local (e.g.: argument)
             && !nodep->varp()->isStatic()  // Not a static variable
             && !nodep->varp()->isClassMember()  // Statically exists in design hierarchy
+            && !nodep->varp()->isUsedVirtIface()  // Not used through a virtual interface
             && !nodep->varp()->valuep()  // Does not have an initializer
         ) {
             UINFO(4, "Consider for localization: " << nodep << endl);
diff --git a/src/V3MergeCond.cpp b/src/V3MergeCond.cpp
index 6103c62b1..fea4cae5e 100644
--- a/src/V3MergeCond.cpp
+++ b/src/V3MergeCond.cpp
@@ -744,7 +744,7 @@ private:
         if (!m_mgFirstp) {
             UASSERT_OBJ(condp, nodep, "Cannot start new list without condition");
             // Mark variable references in the condition
-            condp->foreach([](const AstVarRef* nodep) { nodep->varp()->user1(1); });
+            condp->foreach([](const AstVarRef* nodep) { nodep->varp()->user1(1); });
             // Now check again if mergeable. We need this to pick up assignments to conditions,
             // e.g.: 'c = c ? a : b' at the beginning of the list, which is in fact not mergeable
             // because it updates the condition. We simply bail on these.
diff --git a/src/V3Number.cpp b/src/V3Number.cpp
index 3dcfc88e2..63c0c381d 100644
--- a/src/V3Number.cpp
+++ b/src/V3Number.cpp
@@ -76,7 +76,7 @@ constexpr int MAX_SPRINTF_DOUBLE_SIZE
 //======================================================================
 // Errors
 
-void V3Number::v3errorEnd(std::ostringstream& str) const {
+void V3Number::v3errorEnd(const std::ostringstream& str) const VL_MT_SAFE {
     std::ostringstream nsstr;
     nsstr << str.str();
     if (m_nodep) {
@@ -88,10 +88,10 @@ void V3Number::v3errorEnd(std::ostringstream& str) const {
     }
 }
 
-void V3Number::v3errorEndFatal(std::ostringstream& str) const {
+void V3Number::v3errorEndFatal(const std::ostringstream& str) const VL_MT_SAFE {
     v3errorEnd(str);
     assert(0);  // LCOV_EXCL_LINE
-    VL_UNREACHABLE
+    VL_UNREACHABLE;
 }
 
 //======================================================================
@@ -124,10 +124,8 @@ V3Number::V3Number(AstNode* nodep, const AstNodeDType* nodedtypep) {
     }
 }
 
-void V3Number::V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl) {
-    init(nodep, 0);
+void V3Number::create(const char* sourcep) {
     m_data.setLogic();
-    m_fileline = fl;
     const char* value_startp = sourcep;
     for (const char* cp = sourcep; *cp; cp++) {
         if (*cp == '\'') {
@@ -271,7 +269,8 @@ void V3Number::V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl)
             }
             case '_': break;
             default: {
-                v3error("Illegal character in decimal constant: " << *cp);
+                // Likely impossible as parser prevents hitting it
+                v3error("Illegal character in decimal constant: " << *cp);  // LCOV_EXCL_LINE
                 break;
             }
             }
@@ -348,11 +347,17 @@ void V3Number::V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl)
                 case 'x': setBit(obit++,'x'); setBit(obit++,'x'); setBit(obit++,'x'); setBit(obit++,'x'); break;
                     // clang-format on
                 case '_': break;
-                default: v3error("Illegal character in hex constant: " << *cp);
+                default:
+                    // Likely impossible as parser prevents hitting it
+                    v3error("Illegal character in hex constant: " << *cp);  // LCOV_EXCL_LINE
+                    break;
                 }
                 break;
             }
-            default: v3error("Illegal base character: " << base);
+            default:
+                // Likely impossible as parser prevents hitting it
+                v3error("Illegal base character: " << base);  // LCOV_EXCL_LINE
+                break;
             }
         }
     }
@@ -369,7 +374,7 @@ void V3Number::V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl)
     // m_value[0]);
 }
 
-void V3Number::setNames(AstNode* nodep) {
+void V3Number::nodep(AstNode* nodep) {
     m_nodep = nodep;
     if (!nodep) return;
     m_fileline = nodep->fileline();
@@ -508,7 +513,6 @@ string V3Number::ascii(bool prefixed, bool cleanVerilog) const {
             out << "'";
             if (bitIs0(0)) {
                 out << '0';
-                if (isNull()) out << "[null]";
             } else if (bitIs1(0)) {
                 out << '1';
             } else if (bitIsZ(0)) {
@@ -544,7 +548,13 @@ string V3Number::ascii(bool prefixed, bool cleanVerilog) const {
         // Always deal with 4 bits at once.  Note no 4-state, it's above.
         out << displayed("%0h");
     }
-    if (isNull() && VL_UNCOVERABLE(!isEqZero())) out << "-%E-null-not-zero";
+    if (isNull()) {
+        if (VL_UNCOVERABLE(!isEqZero())) {
+            out << "-%E-null-not-zero";
+        } else {
+            out << " [null]";
+        }
+    }
     return out.str();
 }
 
@@ -600,86 +610,90 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const {
     string str;
     const char code = tolower(pos[0]);
     switch (code) {
-    case 'b': {
-        int bit = width() - 1;
-        if (fmtsize == "0")
-            while (bit && bitIs0(bit)) bit--;
-        for (; bit >= 0; bit--) {
-            if (bitIs0(bit)) {
-                str += '0';
-            } else if (bitIs1(bit)) {
-                str += '1';
-            } else if (bitIsZ(bit)) {
-                str += 'z';
-            } else {
-                str += 'x';
-            }
-        }
-        return str;
-    }
-    case 'o': {
-        int bit = width() - 1;
-        if (fmtsize == "0")
-            while (bit && bitIs0(bit)) bit--;
-        while ((bit % 3) != 2) bit++;
-        for (; bit > 0; bit -= 3) {
-            const int numX = countX(bit - 2, 3);
-            const int numZ = countZ(bit - 2, 3);
-            if (numX == 3 || numX == width() - (bit - 2)) {
-                str += 'x';
-                continue;
-            }
-            if (numZ == 3 || numZ == width() - (bit - 2)) {
-                str += 'z';
-                continue;
-            }
-            if (numX > 0) {
-                str += 'X';
-                continue;
-            }
-            if (numZ > 0) {
-                str += 'Z';
-                continue;
-            }
-            const int v = bitsValue(bit - 2, 3);
-            str += static_cast('0' + v);
-        }
-        return str;
-    }
-    case 'h':
+    case 'b':  // FALLTHRU
+    case 'o':  // FALLTHRU
+    case 'h':  // FALLTHRU
     case 'x': {
         int bit = width() - 1;
-        if (fmtsize == "0")
-            while (bit && bitIs0(bit)) bit--;
-        while ((bit % 4) != 3) bit++;
-        for (; bit > 0; bit -= 4) {
-            const int numX = countX(bit - 3, 4);
-            const int numZ = countZ(bit - 3, 4);
-            if (numX == 4 || numX == width() - (bit - 3)) {
-                str += 'x';
-                continue;
+        if (left || !fmtsize.empty()) {
+            while (bit && bitIs0(bit)) --bit;
+        }
+        switch (code) {
+        case 'b': {
+            for (; bit >= 0; --bit) {
+                if (bitIs0(bit)) {
+                    str += '0';
+                } else if (bitIs1(bit)) {
+                    str += '1';
+                } else if (bitIsZ(bit)) {
+                    str += 'z';
+                } else {
+                    str += 'x';
+                }
             }
-            if (numZ == 4 || numZ == width() - (bit - 3)) {
-                str += 'z';
-                continue;
-            }
-            if (numX > 0) {
-                str += 'X';
-                continue;
-            }
-            if (numZ > 0) {
-                str += 'Z';
-                continue;
-            }
-            const int v = bitsValue(bit - 3, 4);
-            if (v >= 10) {
-                str += static_cast('a' + v - 10);
-            } else {
+            break;
+        }
+        case 'o': {
+            while ((bit % 3) != 2) ++bit;
+            for (; bit > 0; bit -= 3) {
+                const int numX = countX(bit - 2, 3);
+                const int numZ = countZ(bit - 2, 3);
+                if (numX == 3 || numX == width() - (bit - 2)) {
+                    str += 'x';
+                    continue;
+                }
+                if (numZ == 3 || numZ == width() - (bit - 2)) {
+                    str += 'z';
+                    continue;
+                }
+                if (numX > 0) {
+                    str += 'X';
+                    continue;
+                }
+                if (numZ > 0) {
+                    str += 'Z';
+                    continue;
+                }
+                const int v = bitsValue(bit - 2, 3);
                 str += static_cast('0' + v);
             }
+            break;
         }
+        default: {  // h/x
+            while ((bit % 4) != 3) ++bit;
+            for (; bit > 0; bit -= 4) {
+                const int numX = countX(bit - 3, 4);
+                const int numZ = countZ(bit - 3, 4);
+                if (numX == 4 || numX == width() - (bit - 3)) {
+                    str += 'x';
+                    continue;
+                }
+                if (numZ == 4 || numZ == width() - (bit - 3)) {
+                    str += 'z';
+                    continue;
+                }
+                if (numX > 0) {
+                    str += 'X';
+                    continue;
+                }
+                if (numZ > 0) {
+                    str += 'Z';
+                    continue;
+                }
+                const int v = bitsValue(bit - 3, 4);
+                if (v >= 10) {
+                    str += static_cast('a' + v - 10);
+                } else {
+                    str += static_cast('0' + v);
+                }
+            }
+            break;
+        }
+        }  // switch
+        const size_t fmtsizen = static_cast(std::atoi(fmtsize.c_str()));
+        str = displayPad(fmtsizen, (left ? ' ' : '0'), left, str);
         return str;
-    }
+    }  // case b/d/x/o
     case 'c': {
         if (width() > 8) fl->v3warn(WIDTH, "$display-like format of %c format of > 8 bit value");
         const unsigned int v = bitsValue(0, 8);
@@ -711,7 +725,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const {
     case 't':  // Time
     case 'd': {  // Unsigned decimal
         const bool issigned = (code == '~');
-        if (fmtsize == "") {
+        if (fmtsize == "" && !left) {
             const double mantissabits = width() - (issigned ? 1 : 0);
             // To get the number of digits required, we want to compute
             // log10(2**mantissabits) and round it up. To be able to handle
@@ -750,7 +764,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const {
                 }
             }
         }
-        const bool zeropad = fmtsize.length() > 0 && fmtsize[0] == '0';
+        const bool zeropad = fmtsize.length() > 0 && fmtsize[0] == '0' && !left;
         // fmtsize might have changed since we parsed the %fmtsize
         const size_t fmtsizen = static_cast(std::atoi(fmtsize.c_str()));
         str = displayPad(fmtsizen, (zeropad ? '0' : ' '), left, str);
@@ -871,7 +885,7 @@ string V3Number::toDecimalU() const {
 //======================================================================
 // ACCESSORS - as numbers
 
-uint32_t V3Number::toUInt() const {
+uint32_t V3Number::toUInt() const VL_MT_SAFE {
     UASSERT(!isFourState(), "toUInt with 4-state " << *this);
     // We allow wide numbers that represent values <= 32 bits
     for (int i = 1; i < words(); ++i) {
@@ -909,7 +923,7 @@ int32_t V3Number::toSInt() const {
     }
 }
 
-uint64_t V3Number::toUQuad() const {
+uint64_t V3Number::toUQuad() const VL_MT_SAFE {
     UASSERT(!isFourState(), "toUQuad with 4-state " << *this);
     // We allow wide numbers that represent values <= 64 bits
     if (isDouble()) return static_cast(toDouble());
@@ -2171,7 +2185,9 @@ V3Number& V3Number::opAssignNonXZ(const V3Number& lhs, bool ignoreXZ) {
     // to itself; V3Simulate does this when hits "foo=foo;"
     // So no: NUM_ASSERT_OP_ARGS1(lhs);
     if (this != &lhs) {
-        if (isString()) {
+        if (VL_UNLIKELY(lhs.isNull())) {
+            m_data.m_isNull = true;
+        } else if (isString()) {
             if (VL_UNLIKELY(!lhs.isString())) {
                 // Non-compatible types, erase value.
                 m_data.str() = "";
diff --git a/src/V3Number.h b/src/V3Number.h
index f149c6ae6..10032b68d 100644
--- a/src/V3Number.h
+++ b/src/V3Number.h
@@ -196,7 +196,7 @@ public:
         UASSERT(isNumber(), "`num` member accessed when data type is " << m_type);
         return isInlineNumber() ? m_inlineNumber.data() : m_dynamicNumber.data();
     }
-    const ValueAndX* num() const {
+    const ValueAndX* num() const VL_MT_SAFE {
         UASSERT(isNumber(), "`num` member accessed when data type is " << m_type);
         return isInlineNumber() ? m_inlineNumber.data() : m_dynamicNumber.data();
     }
@@ -204,13 +204,13 @@ public:
         UASSERT(isString(), "`str` member accessed when data type is " << m_type);
         return m_string;
     }
-    const std::string& str() const {
+    const std::string& str() const VL_MT_SAFE {
         UASSERT(isString(), "`str` member accessed when data type is " << m_type);
         return m_string;
     }
 
-    int width() const { return m_width; }
-    V3NumberDataType type() const { return m_type; }
+    int width() const VL_MT_SAFE { return m_width; }
+    V3NumberDataType type() const VL_MT_SAFE { return m_type; }
 
     // METHODS
     void resize(int bitsCount) {
@@ -275,19 +275,19 @@ public:
     }
 
 private:
-    static constexpr int bitsToWords(int bitsCount) { return (bitsCount + 31) / 32; }
+    static constexpr int bitsToWords(int bitsCount) VL_MT_SAFE { return (bitsCount + 31) / 32; }
 
-    bool isNumber() const {
+    bool isNumber() const VL_MT_SAFE {
         return m_type == V3NumberDataType::DOUBLE || m_type == V3NumberDataType::LOGIC;
     }
-    bool isInlineNumber() const {
+    bool isInlineNumber() const VL_MT_SAFE {
         return (m_width <= MAX_INLINE_WIDTH)
                && (m_type == V3NumberDataType::DOUBLE || m_type == V3NumberDataType::LOGIC);
     }
-    bool isDynamicNumber() const {
+    bool isDynamicNumber() const VL_MT_SAFE {
         return (m_width > MAX_INLINE_WIDTH) && (m_type == V3NumberDataType::LOGIC);
     }
-    bool isString() const { return m_type == V3NumberDataType::STRING; }
+    bool isString() const VL_MT_SAFE { return m_type == V3NumberDataType::STRING; }
 
     template 
     void initInlineNumber(Args&&... args) {
@@ -338,8 +338,8 @@ class V3Number final {
 
     // MEMBERS
     V3NumberData m_data;
-    AstNode* m_nodep = nullptr;  // Parent node
-    FileLine* m_fileline = nullptr;
+    AstNode* m_nodep = nullptr;  // Parent node - for error reporting only
+    FileLine* m_fileline = nullptr;  // Source location - if no parent node is reasonable
 
     // METHODS
     V3Number& setSingleBits(char value);
@@ -350,8 +350,8 @@ class V3Number final {
     void opCleanThis(bool warnOnTruncation = false);
 
 public:
-    void nodep(AstNode* nodep) { setNames(nodep); }
-    FileLine* fileline() const { return m_fileline; }
+    void nodep(AstNode* nodep);
+    FileLine* fileline() const VL_MT_SAFE { return m_fileline; }
     V3Number& setZero();
     V3Number& setQuad(uint64_t value);
     V3Number& setLong(uint32_t value);
@@ -377,7 +377,7 @@ public:
     }
 
 private:
-    char bitIs(int bit) const {
+    char bitIs(int bit) const VL_MT_SAFE {
         if (bit >= m_data.width() || bit < 0) {
             // We never sign extend
             return '0';
@@ -403,14 +403,14 @@ private:
     }
 
 public:
-    bool bitIs0(int bit) const {
+    bool bitIs0(int bit) const VL_MT_SAFE {
         if (!isNumber()) return false;
         if (bit < 0) return false;
         if (bit >= m_data.width()) return !bitIsXZ(m_data.width() - 1);
         const ValueAndX v = m_data.num()[bit / 32];
         return ((v.m_value & (1UL << (bit & 31))) == 0 && !(v.m_valueX & (1UL << (bit & 31))));
     }
-    bool bitIs1(int bit) const {
+    bool bitIs1(int bit) const VL_MT_SAFE {
         if (!isNumber()) return false;
         if (bit < 0) return false;
         if (bit >= m_data.width()) return false;
@@ -424,21 +424,21 @@ public:
         const ValueAndX v = m_data.num()[bit / 32];
         return ((v.m_value & (1UL << (bit & 31))) && !(v.m_valueX & (1UL << (bit & 31))));
     }
-    bool bitIsX(int bit) const {
+    bool bitIsX(int bit) const VL_MT_SAFE {
         if (!isNumber()) return false;
         if (bit < 0) return false;
         if (bit >= m_data.width()) return bitIsZ(m_data.width() - 1);
         const ValueAndX v = m_data.num()[bit / 32];
         return ((v.m_value & (1UL << (bit & 31))) && (v.m_valueX & (1UL << (bit & 31))));
     }
-    bool bitIsXZ(int bit) const {
+    bool bitIsXZ(int bit) const VL_MT_SAFE {
         if (!isNumber()) return false;
         if (bit < 0) return false;
         if (bit >= m_data.width()) return bitIsXZ(m_data.width() - 1);
         const ValueAndX v = m_data.num()[bit / 32];
         return ((v.m_valueX & (1UL << (bit & 31))));
     }
-    bool bitIsZ(int bit) const {
+    bool bitIsZ(int bit) const VL_MT_SAFE {
         if (!isNumber()) return false;
         if (bit < 0) return false;
         if (bit >= m_data.width()) return bitIsZ(m_data.width() - 1);
@@ -447,17 +447,17 @@ public:
     }
 
 private:
-    uint32_t bitsValue(int lsb, int nbits) const {
+    uint32_t bitsValue(int lsb, int nbits) const VL_MT_SAFE {
         uint32_t v = 0;
         for (int bitn = 0; bitn < nbits; bitn++) { v |= (bitIs1(lsb + bitn) << bitn); }
         return v;
     }
 
-    int countX(int lsb, int nbits) const;
-    int countZ(int lsb, int nbits) const;
+    int countX(int lsb, int nbits) const VL_MT_SAFE;
+    int countZ(int lsb, int nbits) const VL_MT_SAFE;
 
-    int words() const { return ((width() + 31) / 32); }
-    uint32_t hiWordMask() const { return VL_MASK_I(width()); }
+    int words() const VL_MT_SAFE { return ((width() + 31) / 32); }
+    uint32_t hiWordMask() const VL_MT_SAFE { return VL_MASK_I(width()); }
 
     V3Number& opModDivGuts(const V3Number& lhs, const V3Number& rhs, bool is_modulus);
 
@@ -472,12 +472,15 @@ public:
         m_data.num()[0].m_value = value;
         opCleanThis();
     }
-    // Create from a verilog 32'hxxxx number.
-    V3Number(AstNode* nodep, const char* sourcep) { V3NumberCreate(nodep, sourcep, nullptr); }
-    class FileLined {};  // Fileline based errors, for parsing only, otherwise pass nodep
-    V3Number(FileLined, FileLine* fl, const char* sourcep) {
-        V3NumberCreate(nullptr, sourcep, fl);
+    V3Number(FileLine* flp, int width, uint32_t value) {
+        init(nullptr, width, true);
+        m_fileline = flp;
+        m_data.num()[0].m_value = value;
+        opCleanThis();
     }
+    // Create from a verilog 32'hxxxx number.
+    V3Number(AstNode* nodep, const char* sourcep) { create(nodep, sourcep); }
+    V3Number(FileLine* flp, const char* sourcep) { create(flp, sourcep); }
     class VerilogStringLiteral {};  // For creator type-overload selection
     V3Number(VerilogStringLiteral, AstNode* nodep, const string& str);
     class String {};
@@ -518,9 +521,19 @@ public:
     ~V3Number() {}
 
 private:
-    void V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl);
+    void create(AstNode* nodep, const char* sourcep) {
+        init(nodep, 0);
+        m_fileline = nullptr;
+        create(sourcep);
+    }
+    void create(FileLine* flp, const char* sourcep) {
+        init(nullptr, 0);
+        m_fileline = flp;
+        create(sourcep);
+    }
+    void create(const char* sourcep);
     void init(AstNode* nodep, int swidth = -1, bool sized = true) {
-        setNames(nodep);
+        this->nodep(nodep);
         if (swidth >= 0) {
             if (swidth == 0) {
                 swidth = 1;
@@ -535,14 +548,15 @@ private:
             m_data.m_sized = false;
         }
     }
-    void setNames(AstNode* nodep);
     static string displayPad(size_t fmtsize, char pad, bool left, const string& in);
-    string displayed(FileLine* fl, const string& vformat) const;
-    string displayed(const string& vformat) const { return displayed(m_fileline, vformat); }
+    string displayed(FileLine* fl, const string& vformat) const VL_MT_SAFE;
+    string displayed(const string& vformat) const VL_MT_SAFE {
+        return displayed(m_fileline, vformat);
+    }
 
 public:
-    void v3errorEnd(std::ostringstream& sstr) const;
-    void v3errorEndFatal(std::ostringstream& sstr) const VL_ATTR_NORETURN;
+    void v3errorEnd(const std::ostringstream& sstr) const;
+    void v3errorEndFatal(const std::ostringstream& sstr) const VL_ATTR_NORETURN;
     void width(int width, bool sized = true) {
         m_data.m_sized = sized;
         m_data.resize(width);
@@ -557,15 +571,15 @@ public:
     V3Number& setMask(int nbits);  // IE if nbits=1, then 0b1, if 2->0b11, if 3->0b111 etc
 
     // ACCESSORS
-    string ascii(bool prefixed = true, bool cleanVerilog = false) const;
+    string ascii(bool prefixed = true, bool cleanVerilog = false) const VL_MT_SAFE;
     string displayed(AstNode* nodep, const string& vformat) const;
     static bool displayedFmtLegal(char format, bool isScan);  // Is this a valid format letter?
-    int width() const { return m_data.width(); }
+    int width() const VL_MT_SAFE { return m_data.width(); }
     int widthMin() const;  // Minimum width that can represent this number (~== log2(num)+1)
-    bool sized() const { return m_data.m_sized; }
-    bool autoExtend() const { return m_data.m_autoExtend; }
+    bool sized() const VL_MT_SAFE { return m_data.m_sized; }
+    bool autoExtend() const VL_MT_SAFE { return m_data.m_autoExtend; }
     bool isFromString() const { return m_data.m_fromString; }
-    V3NumberDataType dataType() const { return m_data.type(); }
+    V3NumberDataType dataType() const VL_MT_SAFE { return m_data.type(); }
     void dataType(V3NumberDataType newType) {
         if (dataType() == newType) return;
         UASSERT(newType != V3NumberDataType::UNINITIALIZED, "Can't set type to UNINITIALIZED.");
@@ -578,17 +592,17 @@ public:
     }
     // Only correct for parsing of numbers from strings, otherwise not used
     // (use AstConst::isSigned())
-    bool isSigned() const { return m_data.m_signed; }
+    bool isSigned() const VL_MT_SAFE { return m_data.m_signed; }
     void isSigned(bool ssigned) { m_data.m_signed = ssigned; }
-    bool isDouble() const { return dataType() == V3NumberDataType::DOUBLE; }
-    bool isString() const { return dataType() == V3NumberDataType::STRING; }
-    bool isNumber() const {
+    bool isDouble() const VL_MT_SAFE { return dataType() == V3NumberDataType::DOUBLE; }
+    bool isString() const VL_MT_SAFE { return dataType() == V3NumberDataType::STRING; }
+    bool isNumber() const VL_MT_SAFE {
         return m_data.type() == V3NumberDataType::LOGIC
                || m_data.type() == V3NumberDataType::DOUBLE;
     }
-    bool isNegative() const { return !isString() && bitIs1(width() - 1); }
-    bool isNull() const { return m_data.m_isNull; }
-    bool isFourState() const;
+    bool isNegative() const VL_MT_SAFE { return !isString() && bitIs1(width() - 1); }
+    bool isNull() const VL_MT_SAFE { return m_data.m_isNull; }
+    bool isFourState() const VL_MT_SAFE;
     bool hasZ() const {
         if (isString()) return false;
         for (int i = 0; i < words(); i++) {
@@ -597,27 +611,27 @@ public:
         }
         return false;
     }
-    bool isAllZ() const;
-    bool isAllX() const;
-    bool isEqZero() const;
+    bool isAllZ() const VL_MT_SAFE;
+    bool isAllX() const VL_MT_SAFE;
+    bool isEqZero() const VL_MT_SAFE;
     bool isNeqZero() const;
     bool isBitsZero(int msb, int lsb) const;
     bool isEqOne() const;
     bool isEqAllOnes(int optwidth = 0) const;
     bool isCaseEq(const V3Number& rhs) const;  // operator==
     bool isLtXZ(const V3Number& rhs) const;  // operator< with XZ compared
-    bool isAnyX() const;
+    bool isAnyX() const VL_MT_SAFE;
     bool isAnyXZ() const;
-    bool isAnyZ() const;
+    bool isAnyZ() const VL_MT_SAFE;
     bool isMsbXZ() const { return bitIsXZ(m_data.width() - 1); }
     uint32_t toUInt() const;
-    int32_t toSInt() const;
+    int32_t toSInt() const VL_MT_SAFE;
     uint64_t toUQuad() const;
-    int64_t toSQuad() const;
-    string toString() const;
-    string toDecimalS() const;  // return ASCII signed decimal number
-    string toDecimalU() const;  // return ASCII unsigned decimal number
-    double toDouble() const;
+    int64_t toSQuad() const VL_MT_SAFE;
+    string toString() const VL_MT_SAFE;
+    string toDecimalS() const VL_MT_SAFE;  // return ASCII signed decimal number
+    string toDecimalU() const VL_MT_SAFE;  // return ASCII unsigned decimal number
+    double toDouble() const VL_MT_SAFE;
     V3Hash toHash() const;
     uint32_t edataWord(int eword) const;
     uint8_t dataByte(int byte) const;
diff --git a/src/V3OptionParser.cpp b/src/V3OptionParser.cpp
index 1a1f7d84a..cc019b33d 100644
--- a/src/V3OptionParser.cpp
+++ b/src/V3OptionParser.cpp
@@ -55,6 +55,7 @@ struct V3OptionParser::Impl {
     template 
     class ActionOnOff;  // "-opt" and "-no-opt" for bool-ish
     class ActionCbCall;  // Callback without argument for "-opt"
+    class ActionCbFOnOff;  // Callback for "-fopt" and "-fno-opt"
     class ActionCbOnOff;  // Callback for "-opt" and "-no-opt"
     template 
     class ActionCbVal;  // Callback for "-opt val"
@@ -108,6 +109,8 @@ V3OPTION_PARSER_DEF_ACT_CLASS(ActionOnOff, VOptionBool, m_valp->setTrueOrFalse(!
     }
 
 V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbCall, void(void), m_cb(), en::NONE);
+V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbFOnOff, void(bool), m_cb(!hasPrefixFNo(optp)),
+                                 en::FONOFF);
 V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbOnOff, void(bool), m_cb(!hasPrefixNo(optp)), en::ONOFF);
 template <>
 V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbVal, void(int), m_cb(std::atoi(argp)), en::VALUE);
@@ -238,6 +241,7 @@ V3OPTION_PARSER_DEF_OP(OnOff, bool*, ActionOnOff)
 V3OPTION_PARSER_DEF_OP(OnOff, VOptionBool*, ActionOnOff)
 #endif
 V3OPTION_PARSER_DEF_OP(CbCall, Impl::ActionCbCall::CbType, ActionCbCall)
+V3OPTION_PARSER_DEF_OP(CbFOnOff, Impl::ActionCbFOnOff::CbType, ActionCbFOnOff)
 V3OPTION_PARSER_DEF_OP(CbOnOff, Impl::ActionCbOnOff::CbType, ActionCbOnOff)
 V3OPTION_PARSER_DEF_OP(CbVal, Impl::ActionCbVal::CbType, ActionCbVal)
 V3OPTION_PARSER_DEF_OP(CbVal, Impl::ActionCbVal::CbType, ActionCbVal)
diff --git a/src/V3OptionParser.h b/src/V3OptionParser.h
index 2c5dbf6fb..23da89245 100644
--- a/src/V3OptionParser.h
+++ b/src/V3OptionParser.h
@@ -109,7 +109,8 @@ public:
     struct Set {};  // For ActionSet
 
     struct CbCall {};  // For ActionCbCall
-    struct CbOnOff {};  // For ActionOnOff of ActionFOnOff
+    struct CbFOnOff {};  // For ActionCbFOnOff
+    struct CbOnOff {};  // For ActionCbOnOff
     struct CbPartialMatch {};  // For ActionCbPartialMatch
     struct CbPartialMatchVal {};  // For ActionCbPartialMatchVal
     struct CbVal {};  // For ActionCbVal
@@ -134,6 +135,7 @@ public:
 #endif
 
     ActionIfs& operator()(const char* optp, CbCall, std::function) const;
+    ActionIfs& operator()(const char* optp, CbFOnOff, std::function) const;
     ActionIfs& operator()(const char* optp, CbOnOff, std::function) const;
     ActionIfs& operator()(const char* optp, CbVal, std::function) const;
     ActionIfs& operator()(const char* optp, CbVal, std::function) const;
@@ -153,6 +155,7 @@ public:
     const auto FOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::FOnOff{}; \
     const auto OnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::OnOff{}; \
     const auto CbCall VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbCall{}; \
+    const auto CbFOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbFOnOff{}; \
     const auto CbOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbOnOff{}; \
     const auto CbPartialMatch VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbPartialMatch{}; \
     const auto CbPartialMatchVal VL_ATTR_UNUSED \
diff --git a/src/V3Options.cpp b/src/V3Options.cpp
index 4456f601e..f009a5f2d 100644
--- a/src/V3Options.cpp
+++ b/src/V3Options.cpp
@@ -395,7 +395,7 @@ void V3Options::addForceInc(const string& filename) { m_forceIncs.push_back(file
 
 void V3Options::addArg(const string& arg) { m_impp->m_allArgs.push_back(arg); }
 
-string V3Options::allArgsString() const {
+string V3Options::allArgsString() const VL_MT_SAFE {
     string out;
     for (const string& i : m_impp->m_allArgs) {
         if (out != "") out += " ";
@@ -604,6 +604,7 @@ V3LangCode V3Options::fileLanguage(const string& filename) {
 // Environment
 
 string V3Options::getenvBuiltins(const string& var) {
+    // If update below, also update V3Options::showVersion()
     if (var == "MAKE") {
         return getenvMAKE();
     } else if (var == "PERL") {
@@ -711,6 +712,17 @@ string V3Options::getenvVERILATOR_ROOT() {
     return var;
 }
 
+string V3Options::getSupported(const string& var) {
+    // If update below, also update V3Options::showVersion()
+    if (var == "COROUTINES" && coroutineSupport()) {
+        return "1";
+    } else if (var == "SYSTEMC" && systemCFound()) {
+        return "1";
+    } else {
+        return "";
+    }
+}
+
 bool V3Options::systemCSystemWide() {
 #ifdef HAVE_SYSTEMC
     return true;
@@ -724,6 +736,14 @@ bool V3Options::systemCFound() {
             || (!getenvSYSTEMC_INCLUDE().empty() && !getenvSYSTEMC_LIBDIR().empty()));
 }
 
+bool V3Options::coroutineSupport() {
+#ifdef HAVE_COROUTINES
+    return true;
+#else
+    return false;
+#endif
+}
+
 //######################################################################
 // V3 Options notification methods
 
@@ -733,7 +753,7 @@ void V3Options::notify() {
 
     if (!outFormatOk() && v3Global.opt.main()) ccSet();  // --main implies --cc if not provided
     if (!outFormatOk() && !cdc() && !dpiHdrOnly() && !lintOnly() && !preprocOnly() && !xmlOnly()) {
-        v3fatal("verilator: Need --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, "
+        v3fatal("verilator: Need --binary, --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, "
                 "--xml-only or --E option");
     }
 
@@ -820,6 +840,14 @@ void V3Options::notify() {
 
     // Mark options as available
     m_available = true;
+
+    // --dump-tree-dot will turn on tree dumping.
+    if (!m_dumpLevel.count("tree") && m_dumpLevel.count("tree-dot")) {
+        m_dumpLevel["tree"] = m_dumpLevel["tree-dot"];
+    }
+
+    // Preprocessor defines based on options used
+    if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1");
 }
 
 //######################################################################
@@ -1008,6 +1036,12 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
         m_bboxUnsup = flag;
         FileLine::globalWarnOff(V3ErrorCode::E_UNSUPPORTED, true);
     });
+    DECL_OPTION("-binary", CbCall, [this]() {
+        m_build = true;
+        m_exe = true;
+        m_main = true;
+        if (m_timing.isDefault()) m_timing = VOptionBool::OPT_TRUE;
+    });
     DECL_OPTION("-build", Set, &m_build);
     DECL_OPTION("-build-dep-bin", Set, &m_buildDepBin);
     DECL_OPTION("-build-jobs", CbVal, [this, fl](const char* valp) {
@@ -1113,8 +1147,22 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
     DECL_OPTION("-fcase", FOnOff, &m_fCase);
     DECL_OPTION("-fcombine", FOnOff, &m_fCombine);
     DECL_OPTION("-fconst", FOnOff, &m_fConst);
+    DECL_OPTION("-fconst-before-dfg", FOnOff, &m_fConstBeforeDfg);
     DECL_OPTION("-fconst-bit-op-tree", FOnOff, &m_fConstBitOpTree);
     DECL_OPTION("-fdedup", FOnOff, &m_fDedupe);
+    DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) {
+        m_fDfgPreInline = flag;
+        m_fDfgPostInline = flag;
+    });
+    DECL_OPTION("-fdfg-peephole", FOnOff, &m_fDfgPeephole);
+    DECL_OPTION("-fdfg-peephole-", CbPartialMatch, [this](const char* optp) {  //
+        m_fDfgPeepholeDisabled.erase(optp);
+    });
+    DECL_OPTION("-fno-dfg-peephole-", CbPartialMatch, [this](const char* optp) {  //
+        m_fDfgPeepholeDisabled.emplace(optp);
+    });
+    DECL_OPTION("-fdfg-pre-inline", FOnOff, &m_fDfgPreInline);
+    DECL_OPTION("-fdfg-post-inline", FOnOff, &m_fDfgPostInline);
     DECL_OPTION("-fexpand", FOnOff, &m_fExpand);
     DECL_OPTION("-fgate", FOnOff, &m_fGate);
     DECL_OPTION("-finline", FOnOff, &m_fInline);
@@ -1143,6 +1191,10 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
         cout << V3Options::getenvBuiltins(valp) << endl;
         std::exit(0);
     });
+    DECL_OPTION("-get-supported", CbVal, [](const char* valp) {
+        cout << V3Options::getSupported(valp) << endl;
+        std::exit(0);
+    });
 
     DECL_OPTION("-hierarchical", OnOff, &m_hierarchical);
     DECL_OPTION("-hierarchical-block", CbVal, [this](const char* valp) {
@@ -1249,7 +1301,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
         }
     });
     DECL_OPTION("-o", Set, &m_exeName);
-    DECL_OPTION("-order-clock-delay", OnOff, &m_orderClockDly);
+    DECL_OPTION("-order-clock-delay", CbOnOff, [fl](bool /*flag*/) {
+        fl->v3warn(DEPRECATED, "Option order-clock-delay is deprecated and has no effect.");
+    });
     DECL_OPTION("-output-split", Set, &m_outputSplit);
     DECL_OPTION("-output-split-cfuncs", CbVal, [this, fl](const char* valp) {
         m_outputSplitCFuncs = std::atoi(valp);
@@ -1379,6 +1433,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
             m_timeOverridePrec = prec;
         }
     });
+    DECL_OPTION("-timing", OnOff, &m_timing);
     DECL_OPTION("-top-module", Set, &m_topModule);
     DECL_OPTION("-top", Set, &m_topModule);
     DECL_OPTION("-trace", OnOff, &m_trace);
@@ -1433,6 +1488,11 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
         FileLine::globalWarnLintOff(false);
         FileLine::globalWarnStyleOff(false);
     });
+    DECL_OPTION("-Werror-UNUSED", CbCall, []() {
+        V3Error::pretendError(V3ErrorCode::UNUSEDGENVAR, true);
+        V3Error::pretendError(V3ErrorCode::UNUSEDPARAM, true);
+        V3Error::pretendError(V3ErrorCode::UNUSEDSIGNAL, true);
+    });
     DECL_OPTION("-Werror-", CbPartialMatch, [this, fl](const char* optp) {
         const V3ErrorCode code(optp);
         if (code == V3ErrorCode::EC_ERROR) {
@@ -1463,6 +1523,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
         FileLine::globalWarnStyleOff(true);
     });
     DECL_OPTION("-Wno-style", CbCall, []() { FileLine::globalWarnStyleOff(true); });
+    DECL_OPTION("-Wno-UNUSED", CbCall, []() { FileLine::globalWarnUnusedOff(true); });
     DECL_OPTION("-Wwarn-", CbPartialMatch, [this, fl, &parser](const char* optp) {
         const V3ErrorCode code{optp};
         if (code == V3ErrorCode::EC_ERROR) {
@@ -1478,6 +1539,12 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
     });
     DECL_OPTION("-Wwarn-lint", CbCall, []() { FileLine::globalWarnLintOff(false); });
     DECL_OPTION("-Wwarn-style", CbCall, []() { FileLine::globalWarnStyleOff(false); });
+    DECL_OPTION("-Wwarn-UNUSED", CbCall, []() {
+        FileLine::globalWarnUnusedOff(false);
+        V3Error::pretendError(V3ErrorCode::UNUSEDGENVAR, false);
+        V3Error::pretendError(V3ErrorCode::UNUSEDSIGNAL, false);
+        V3Error::pretendError(V3ErrorCode::UNUSEDPARAM, false);
+    });
     DECL_OPTION("-waiver-output", Set, &m_waiverOutput);
 
     DECL_OPTION("-x-assign", CbVal, [this, fl](const char* valp) {
@@ -1527,14 +1594,8 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
             ++i;
             int val = 0;
             if (i < argc && isdigit(argv[i][0])) {
-                val = atoi(argv[i]);
-                if (val < 0) {
-                    fl->v3error("-j requires a non-negative integer argument, but '"
-                                << argv[i] << "' was passed");
-                    val = 1;  // Fall-back value, though we will exit on error.
-                } else if (val == 0) {
-                    val = std::thread::hardware_concurrency();
-                }
+                val = atoi(argv[i]);  // Can't be negative due to isdigit above
+                if (val == 0) val = std::thread::hardware_concurrency();
                 ++i;
             }
             if (m_buildJobs == -1) m_buildJobs = val;
@@ -1749,6 +1810,7 @@ void V3Options::showVersion(bool verbose) {
     cout << "    VERILATOR_ROOT     = " << DEFENV_VERILATOR_ROOT << endl;
     cout << "    SystemC system-wide = " << cvtToStr(systemCSystemWide()) << endl;
 
+    // If update below, also update V3Options::getenvBuiltins()
     cout << endl;
     cout << "Environment:\n";
     cout << "    MAKE               = " << V3Os::getenvStr("MAKE", "") << endl;
@@ -1761,9 +1823,11 @@ void V3Options::showVersion(bool verbose) {
     cout << "    VERILATOR_BIN      = " << V3Os::getenvStr("VERILATOR_BIN", "") << endl;
     cout << "    VERILATOR_ROOT     = " << V3Os::getenvStr("VERILATOR_ROOT", "") << endl;
 
+    // If update below, also update V3Options::getSupported()
     cout << endl;
-    cout << "Features (based on environment or compiled-in support):\n";
-    cout << "    SystemC found      = " << cvtToStr(systemCFound()) << endl;
+    cout << "Supported features (compiled-in or forced by environment):\n";
+    cout << "    COROUTINES         = " << getSupported("COROUTINES") << endl;
+    cout << "    SYSTEMC            = " << getSupported("SYSTEMC") << endl;
 }
 
 //======================================================================
@@ -1800,29 +1864,29 @@ void V3Options::setDebugMode(int level) {
     cout << "Starting " << version() << endl;
 }
 
-unsigned V3Options::debugLevel(const string& tag) const {
+unsigned V3Options::debugLevel(const string& tag) const VL_MT_SAFE {
     const auto iter = m_debugLevel.find(tag);
     return iter != m_debugLevel.end() ? iter->second : V3Error::debugDefault();
 }
 
-unsigned V3Options::debugSrcLevel(const string& srcfile_path) const {
+unsigned V3Options::debugSrcLevel(const string& srcfile_path) const VL_MT_SAFE {
     // For simplicity, calling functions can just use __FILE__ for srcfile.
     // That means we need to strip the filenames: ../Foo.cpp -> Foo
     return debugLevel(V3Os::filenameNonDirExt(srcfile_path));
 }
 
-unsigned V3Options::dumpLevel(const string& tag) const {
+unsigned V3Options::dumpLevel(const string& tag) const VL_MT_SAFE {
     const auto iter = m_dumpLevel.find(tag);
     return iter != m_dumpLevel.end() ? iter->second : 0;
 }
 
-unsigned V3Options::dumpSrcLevel(const string& srcfile_path) const {
+unsigned V3Options::dumpSrcLevel(const string& srcfile_path) const VL_MT_SAFE {
     // For simplicity, calling functions can just use __FILE__ for srcfile.
     // That means we need to strip the filenames: ../Foo.cpp -> Foo
     return dumpLevel(V3Os::filenameNonDirExt(srcfile_path));
 }
 
-bool V3Options::dumpTreeAddrids() const {
+bool V3Options::dumpTreeAddrids() const VL_MT_SAFE {
     static int level = -1;
     if (VL_UNLIKELY(level < 0)) {
         const unsigned value = dumpLevel("tree-addrids");
@@ -1842,6 +1906,8 @@ void V3Options::optimize(int level) {
     m_fConst = flag;
     m_fConstBitOpTree = flag;
     m_fDedupe = flag;
+    m_fDfgPreInline = flag;
+    m_fDfgPostInline = flag;
     m_fExpand = flag;
     m_fGate = flag;
     m_fInline = flag;
diff --git a/src/V3Options.h b/src/V3Options.h
index 782987d46..c2c3852fc 100644
--- a/src/V3Options.h
+++ b/src/V3Options.h
@@ -146,7 +146,7 @@ public:
         static const char* const names[] = {"VerilatedVcd", "VerilatedFst"};
         return names[m_e];
     }
-    string sourceName() const {
+    string sourceName() const VL_MT_SAFE {
         static const char* const names[] = {"verilated_vcd", "verilated_fst"};
         return names[m_e];
     }
@@ -214,6 +214,7 @@ private:
     DebugLevelMap m_dumpLevel;  // argument: --dumpi- 
     std::map m_parameters;  // Parameters
     std::map m_hierBlocks;  // main switch: --hierarchical-block
+    V3StringSet m_fDfgPeepholeDisabled; // argument: -f[no-]dfg-peephole-
 
     bool m_preprocOnly = false;     // main switch: -E
     bool m_makePhony = false;       // main switch: -MP
@@ -249,7 +250,6 @@ private:
     bool m_lintOnly = false;        // main switch: --lint-only
     bool m_gmake = false;           // main switch: --make gmake
     bool m_main = false;            // main swithc: --main
-    bool m_orderClockDly = true;    // main switch: --order-clock-delay
     bool m_outFormatOk = false;     // main switch: --cc, --sc or --sp was specified
     bool m_pedantic = false;        // main switch: --Wpedantic
     bool m_pinsScUint = false;      // main switch: --pins-sc-uint
@@ -274,6 +274,7 @@ private:
     bool m_threadsCoarsen = true;   // main switch: --threads-coarsen
     bool m_threadsDpiPure = true;   // main switch: --threads-dpi all/pure
     bool m_threadsDpiUnpure = false;  // main switch: --threads-dpi all
+    VOptionBool m_timing;           // main switch: --timing
     bool m_trace = false;           // main switch: --trace
     bool m_traceCoverage = false;   // main switch: --trace-coverage
     bool m_traceParams = true;      // main switch: --trace-params
@@ -348,8 +349,12 @@ private:
     bool m_fCase;        // main switch: -fno-case: case tree conversion
     bool m_fCombine;     // main switch: -fno-combine: common icode packing
     bool m_fConst;       // main switch: -fno-const: constant folding
+    bool m_fConstBeforeDfg = true;  // main switch: -fno-const-before-dfg for testing only!
     bool m_fConstBitOpTree;  // main switch: -fno-const-bit-op-tree constant bit op tree
     bool m_fDedupe;      // main switch: -fno-dedupe: logic deduplication
+    bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole
+    bool m_fDfgPreInline;    // main switch: -fno-dfg-pre-inline and -fno-dfg
+    bool m_fDfgPostInline;   // main switch: -fno-dfg-post-inline and -fno-dfg
     bool m_fExpand;      // main switch: -fno-expand: expansion of C macros
     bool m_fGate;        // main switch: -fno-gate: gate wire elimination
     bool m_fInline;      // main switch: -fno-inline: module inlining
@@ -396,10 +401,10 @@ public:
     V3Options();
     ~V3Options();
     void setDebugMode(int level);
-    unsigned debugLevel(const string& tag) const;
-    unsigned debugSrcLevel(const string& srcfile_path) const;
-    unsigned dumpLevel(const string& tag) const;
-    unsigned dumpSrcLevel(const string& srcfile_path) const;
+    unsigned debugLevel(const string& tag) const VL_MT_SAFE;
+    unsigned debugSrcLevel(const string& srcfile_path) const VL_MT_SAFE;
+    unsigned dumpLevel(const string& tag) const VL_MT_SAFE;
+    unsigned dumpSrcLevel(const string& srcfile_path) const VL_MT_SAFE;
 
     // METHODS
     void addCppFile(const string& filename);
@@ -411,7 +416,7 @@ public:
     void addNoClocker(const string& signame);
     void addVFile(const string& filename);
     void addForceInc(const string& filename);
-    bool available() const { return m_available; }
+    bool available() const VL_MT_SAFE { return m_available; }
     void ccSet();
     void notify();
 
@@ -421,8 +426,8 @@ public:
     bool preprocNoLine() const { return m_preprocNoLine; }
     bool underlineZero() const { return m_underlineZero; }
     string flags() const { return m_flags; }
-    bool systemC() const { return m_systemC; }
-    bool savable() const { return m_savable; }
+    bool systemC() const VL_MT_SAFE { return m_systemC; }
+    bool savable() const VL_MT_SAFE { return m_savable; }
     bool stats() const { return m_stats; }
     bool statsVars() const { return m_statsVars; }
     bool structsPacked() const { return m_structsPacked; }
@@ -435,38 +440,43 @@ public:
     void buildDepBin(const string& flag) { m_buildDepBin = flag; }
     bool cdc() const { return m_cdc; }
     bool cmake() const { return m_cmake; }
-    bool context() const { return m_context; }
-    bool coverage() const { return m_coverageLine || m_coverageToggle || m_coverageUser; }
+    bool context() const VL_MT_SAFE { return m_context; }
+    bool coverage() const VL_MT_SAFE {
+        return m_coverageLine || m_coverageToggle || m_coverageUser;
+    }
     bool coverageLine() const { return m_coverageLine; }
     bool coverageToggle() const { return m_coverageToggle; }
     bool coverageUnderscore() const { return m_coverageUnderscore; }
     bool coverageUser() const { return m_coverageUser; }
-    bool debugCheck() const { return m_debugCheck; }
+    bool debugCheck() const VL_MT_SAFE { return m_debugCheck; }
     bool debugCollision() const { return m_debugCollision; }
-    bool debugEmitV() const { return m_debugEmitV; }
+    bool debugEmitV() const VL_MT_SAFE { return m_debugEmitV; }
     bool debugExitParse() const { return m_debugExitParse; }
     bool debugExitUvm() const { return m_debugExitUvm; }
     bool debugLeak() const { return m_debugLeak; }
     bool debugNondeterminism() const { return m_debugNondeterminism; }
     bool debugPartition() const { return m_debugPartition; }
-    bool debugProtect() const { return m_debugProtect; }
+    bool debugProtect() const VL_MT_SAFE { return m_debugProtect; }
     bool debugSelfTest() const { return m_debugSelfTest; }
-    bool decoration() const { return m_decoration; }
+    bool decoration() const VL_MT_SAFE { return m_decoration; }
     bool dpiHdrOnly() const { return m_dpiHdrOnly; }
     bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); }
+    bool dumpTreeDot() const {
+        return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot");
+    }
     bool exe() const { return m_exe; }
     bool flatten() const { return m_flatten; }
     bool gmake() const { return m_gmake; }
     bool threadsDpiPure() const { return m_threadsDpiPure; }
     bool threadsDpiUnpure() const { return m_threadsDpiUnpure; }
     bool threadsCoarsen() const { return m_threadsCoarsen; }
+    VOptionBool timing() const { return m_timing; }
     bool trace() const { return m_trace; }
     bool traceCoverage() const { return m_traceCoverage; }
     bool traceParams() const { return m_traceParams; }
     bool traceStructs() const { return m_traceStructs; }
     bool traceUnderscore() const { return m_traceUnderscore; }
     bool main() const { return m_main; }
-    bool orderClockDly() const { return m_orderClockDly; }
     bool outFormatOk() const { return m_outFormatOk; }
     bool keepTempFiles() const { return (V3Error::debugDefault() != 0); }
     bool pedantic() const { return m_pedantic; }
@@ -479,22 +489,23 @@ public:
     bool profExec() const { return m_profExec; }
     bool profPgo() const { return m_profPgo; }
     bool usesProfiler() const { return profExec() || profPgo(); }
-    bool protectIds() const { return m_protectIds; }
+    bool protectIds() const VL_MT_SAFE { return m_protectIds; }
     bool allPublic() const { return m_public; }
     bool publicFlatRW() const { return m_publicFlatRW; }
-    bool lintOnly() const { return m_lintOnly; }
+    bool lintOnly() const VL_MT_SAFE { return m_lintOnly; }
     bool ignc() const { return m_ignc; }
-    bool quietExit() const { return m_quietExit; }
+    bool quietExit() const VL_MT_SAFE { return m_quietExit; }
     bool reportUnoptflat() const { return m_reportUnoptflat; }
     bool verilate() const { return m_verilate; }
     bool vpi() const { return m_vpi; }
     bool xInitialEdge() const { return m_xInitialEdge; }
     bool xmlOnly() const { return m_xmlOnly; }
+    bool topIfacesSupported() const { return lintOnly() && !hierarchical(); }
 
-    int buildJobs() const { return m_buildJobs; }
+    int buildJobs() const VL_MT_SAFE { return m_buildJobs; }
     int convergeLimit() const { return m_convergeLimit; }
     int coverageMaxWidth() const { return m_coverageMaxWidth; }
-    bool dumpTreeAddrids() const;
+    bool dumpTreeAddrids() const VL_MT_SAFE;
     int expandLimit() const { return m_expandLimit; }
     int gateStmts() const { return m_gateStmts; }
     int ifDepth() const { return m_ifDepth; }
@@ -509,7 +520,7 @@ public:
     int pinsBv() const { return m_pinsBv; }
     int reloopLimit() const { return m_reloopLimit; }
     VOptionBool skipIdentical() const { return m_skipIdentical; }
-    int threads() const { return m_threads; }
+    int threads() const VL_MT_SAFE { return m_threads; }
     int threadsMaxMTasks() const { return m_threadsMaxMTasks; }
     bool mtasks() const { return (m_threads > 1); }
     VTimescale timeDefaultPrec() const { return m_timeDefaultPrec; }
@@ -550,10 +561,10 @@ public:
         }
         return libName;
     }
-    string makeDir() const { return m_makeDir; }
-    string modPrefix() const { return m_modPrefix; }
+    string makeDir() const VL_MT_SAFE { return m_makeDir; }
+    string modPrefix() const VL_MT_SAFE { return m_modPrefix; }
     string pipeFilter() const { return m_pipeFilter; }
-    string prefix() const { return m_prefix; }
+    string prefix() const VL_MT_SAFE { return m_prefix; }
     // Not just called protectKey() to avoid bugs of not using protectKeyDefaulted()
     bool protectKeyProvided() const { return !m_protectKey.empty(); }
     string protectKeyDefaulted();  // Set default key if not set by user
@@ -590,8 +601,15 @@ public:
     bool fCase() const { return m_fCase; }
     bool fCombine() const { return m_fCombine; }
     bool fConst() const { return m_fConst; }
+    bool fConstBeforeDfg() const { return m_fConstBeforeDfg; }
     bool fConstBitOpTree() const { return m_fConstBitOpTree; }
     bool fDedupe() const { return m_fDedupe; }
+    bool fDfgPeephole() const { return m_fDfgPeephole; }
+    bool fDfgPreInline() const { return m_fDfgPreInline; }
+    bool fDfgPostInline() const { return m_fDfgPostInline; }
+    bool fDfgPeepholeEnabled(const std::string& name) const {
+        return !m_fDfgPeepholeDisabled.count(name);
+    }
     bool fExpand() const { return m_fExpand; }
     bool fGate() const { return m_fGate; }
     bool fInline() const { return m_fInline; }
@@ -611,24 +629,24 @@ public:
     string traceClassBase() const { return m_traceFormat.classBase(); }
     string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }
     string traceSourceBase() const { return m_traceFormat.sourceName(); }
-    string traceSourceLang() const {
+    string traceSourceLang() const VL_MT_SAFE {
         return m_traceFormat.sourceName() + (systemC() ? "_sc" : "_c");
     }
 
     bool hierarchical() const { return m_hierarchical; }
     int hierChild() const { return m_hierChild; }
-    bool hierTop() const { return !m_hierChild && !m_hierBlocks.empty(); }
+    bool hierTop() const VL_MT_SAFE { return !m_hierChild && !m_hierBlocks.empty(); }
     const V3HierBlockOptSet& hierBlocks() const { return m_hierBlocks; }
     // Directory to save .tree, .dot, .dat, .vpp for hierarchical block top
     // Returns makeDir() unless top module of hierarchical verilation.
-    string hierTopDataDir() const {
+    string hierTopDataDir() const VL_MT_SAFE {
         return hierTop() ? (makeDir() + '/' + prefix() + "__hier.dir") : makeDir();
     }
 
     // METHODS (from main)
     static string version();
     static string argString(int argc, char** argv);  ///< Return list of arguments as simple string
-    string allArgsString() const;  ///< Return all passed arguments as simple string
+    string allArgsString() const VL_MT_SAFE;  ///< Return all passed arguments as simple string
     // Return options for child hierarchical blocks when forTop==false, otherwise returns args for
     // the top module.
     string allArgsStringForHierBlock(bool forTop) const;
@@ -648,8 +666,10 @@ public:
     static string getenvSYSTEMC_INCLUDE();
     static string getenvSYSTEMC_LIBDIR();
     static string getenvVERILATOR_ROOT();
+    static string getSupported(const string& var);
     static bool systemCSystemWide();
     static bool systemCFound();  // SystemC installed, or environment points to it
+    static bool coroutineSupport();  // Compiler supports coroutines
 
     // METHODS (file utilities using these options)
     string fileExists(const string& filename);
diff --git a/src/V3Order.cpp b/src/V3Order.cpp
index 471012b73..88283587b 100644
--- a/src/V3Order.cpp
+++ b/src/V3Order.cpp
@@ -18,11 +18,6 @@
 //  Compute near optimal scheduling of always/wire statements
 //  Make a graph of the entire netlist
 //
-//      Add master "*INPUTS*" vertex.
-//      For inputs on top level
-//          Add vertex for each input var.
-//              Add edge INPUTS->var_vertex
-//
 //      For seq logic
 //          Add logic_sensitive_vertex for this list of SenItems
 //              Add edge for each sensitive_var->logic_sensitive_vertex
@@ -91,8 +86,10 @@
 #include "V3GraphStream.h"
 #include "V3List.h"
 #include "V3OrderGraph.h"
+#include "V3OrderMoveGraph.h"
 #include "V3Partition.h"
 #include "V3PartitionGraph.h"
+#include "V3Sched.h"
 #include "V3SenTree.h"
 #include "V3SplitVar.h"
 #include "V3Stats.h"
@@ -104,203 +101,10 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 
 VL_DEFINE_DEBUG_FUNCTIONS;
 
-//######################################################################
-// Utilities
-
-static bool domainsExclusive(const AstSenTree* fromp, const AstSenTree* top) {
-    // Return 'true' if we can prove that both 'from' and 'to' cannot both
-    // be active on the same eval pass, or false if we can't prove this.
-    //
-    // This detects the case of 'always @(posedge clk)'
-    // and 'always @(negedge clk)' being exclusive. It also detects
-    // that initial/settle blocks and post-initial blocks are exclusive.
-    //
-    // Are there any other cases we need to handle? Maybe not,
-    // because these are not exclusive:
-    //   always @(posedge A or posedge B)
-    //   always @(negedge A)
-    //
-    // ... unless you know more about A and B, which sounds hard.
-
-    const bool toInitial = top->hasInitial() || top->hasSettle();
-    const bool fromInitial = fromp->hasInitial() || fromp->hasSettle();
-    if (toInitial != fromInitial) return true;
-
-    const AstSenItem* const fromSenListp = fromp->sensesp();
-    const AstSenItem* const toSenListp = top->sensesp();
-
-    UASSERT_OBJ(fromSenListp, fromp, "sensitivity list empty");
-    UASSERT_OBJ(toSenListp, top, "sensitivity list empty");
-
-    if (fromSenListp->nextp()) return false;
-    if (toSenListp->nextp()) return false;
-
-    const AstNodeVarRef* const fromVarrefp = fromSenListp->varrefp();
-    const AstNodeVarRef* const toVarrefp = toSenListp->varrefp();
-    if (!fromVarrefp || !toVarrefp) return false;
-
-    // We know nothing about the relationship between different clocks here,
-    // so give up on proving anything.
-    if (fromVarrefp->varScopep() != toVarrefp->varScopep()) return false;
-
-    return fromSenListp->edgeType().exclusiveEdge(toSenListp->edgeType());
-}
-
-// Predicate returning true if the LHS of the given assignment is a signal marked as clocker
-static bool isClockerAssignment(AstNodeAssign* nodep) {
-    class Visitor final : public VNVisitor {
-    public:
-        bool m_clkAss = false;  // There is signals marked as clocker in the assignment
-    private:
-        // METHODS
-        void visit(AstNodeAssign* nodep) override {
-            if (const AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) {
-                if (varrefp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) {
-                    m_clkAss = true;
-                    UINFO(6, "node was marked as clocker " << varrefp << endl);
-                }
-            }
-            iterateChildren(nodep->rhsp());
-        }
-        void visit(AstNodeMath*) override {}  // Accelerate
-        void visit(AstNode* nodep) override { iterateChildren(nodep); }
-    } visitor;
-    visitor.iterate(nodep);
-    return visitor.m_clkAss;
-}
-
-//######################################################################
-// Functions for above graph classes
-
-void OrderGraph::loopsVertexCb(V3GraphVertex* vertexp) {
-    if (debug()) cout << "-Info-Loop: " << vertexp << "\n";
-    if (OrderLogicVertex* const vvertexp = dynamic_cast(vertexp)) {
-        std::cerr << vvertexp->nodep()->fileline()->warnOther()
-                  << "     Example path: " << vvertexp->nodep()->typeName() << endl;
-    }
-    if (OrderVarVertex* const vvertexp = dynamic_cast(vertexp)) {
-        std::cerr << vvertexp->varScp()->fileline()->warnOther()
-                  << "     Example path: " << vvertexp->varScp()->prettyName() << endl;
-    }
-}
-
-//######################################################################
-// The class is used for propagating the clocker attribute for further
-// avoiding marking clock signals as circular.
-// Transformation:
-//    while (newClockerMarked)
-//        check all assignments
-//            if RHS is marked as clocker:
-//                mark LHS as clocker as well.
-//                newClockerMarked = true;
-//
-// In addition it also check whether clock and data signals are mixed, and
-// produce a CLKDATA warning if so.
-//
-
-class OrderClkMarkVisitor final : public VNVisitor {
-    bool m_hasClk = false;  // flag indicating whether there is clock signal on rhs
-    bool m_inClocked = false;  // Currently inside a sequential block
-    bool m_newClkMarked;  // Flag for deciding whether a new run is needed
-    bool m_inAss = false;  // Currently inside of a assignment
-    int m_childClkWidth = 0;  // If in hasClk, width of clock signal in child
-
-    // METHODS
-
-    void visit(AstNodeAssign* nodep) override {
-        m_hasClk = false;
-        m_inAss = true;
-        m_childClkWidth = 0;
-        iterateAndNextNull(nodep->rhsp());
-        m_inAss = false;
-        if (m_hasClk) {
-            // do the marking
-            if (nodep->lhsp()->width() > m_childClkWidth) {
-                nodep->v3warn(CLKDATA, "Clock is assigned to part of data signal "
-                                           << nodep->lhsp()->prettyNameQ());
-                UINFO(4, "CLKDATA: lhs with width " << nodep->lhsp()->width() << endl);
-                UINFO(4, "     but rhs clock with width " << m_childClkWidth << endl);
-                return;  // skip the marking
-            }
-
-            const AstVarRef* const lhsp = VN_CAST(nodep->lhsp(), VarRef);
-            if (lhsp && (lhsp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_UNKNOWN)) {
-                lhsp->varp()->attrClocker(VVarAttrClocker::CLOCKER_YES);  // mark as clocker
-                m_newClkMarked = true;  // enable a further run since new clocker is marked
-                UINFO(5, "node is newly marked as clocker by assignment " << lhsp << endl);
-            }
-        }
-    }
-    void visit(AstVarRef* nodep) override {
-        if (m_inAss && nodep->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) {
-            if (m_inClocked) {
-                nodep->v3warn(CLKDATA,
-                              "Clock used as data (on rhs of assignment) in sequential block "
-                                  << nodep->prettyNameQ());
-            } else {
-                m_hasClk = true;
-                m_childClkWidth = nodep->width();  // Pass up
-                UINFO(5, "node is already marked as clocker " << nodep << endl);
-            }
-        }
-    }
-    void visit(AstConcat* nodep) override {
-        if (m_inAss) {
-            iterateAndNextNull(nodep->lhsp());
-            const int lw = m_childClkWidth;
-            iterateAndNextNull(nodep->rhsp());
-            const int rw = m_childClkWidth;
-            m_childClkWidth = lw + rw;  // Pass up
-        }
-    }
-    void visit(AstNodeSel* nodep) override {
-        if (m_inAss) {
-            iterateChildren(nodep);
-            // Pass up result width
-            if (m_childClkWidth > nodep->width()) m_childClkWidth = nodep->width();
-        }
-    }
-    void visit(AstSel* nodep) override {
-        if (m_inAss) {
-            iterateChildren(nodep);
-            if (m_childClkWidth > nodep->width()) m_childClkWidth = nodep->width();
-        }
-    }
-    void visit(AstReplicate* nodep) override {
-        if (m_inAss) {
-            iterateChildren(nodep);
-            if (VN_IS(nodep->rhsp(), Const)) {
-                m_childClkWidth = m_childClkWidth * VN_AS(nodep->rhsp(), Const)->toUInt();
-            } else {
-                m_childClkWidth = nodep->width();  // can not check in this case.
-            }
-        }
-    }
-    void visit(AstActive* nodep) override {
-        m_inClocked = nodep->hasClocked();
-        iterateChildren(nodep);
-        m_inClocked = false;
-    }
-    void visit(AstNode* nodep) override { iterateChildren(nodep); }
-
-    // CONSTRUCTORS
-    explicit OrderClkMarkVisitor(AstNode* nodep) {
-        do {
-            m_newClkMarked = false;
-            iterate(nodep);
-        } while (m_newClkMarked);
-    }
-    ~OrderClkMarkVisitor() override = default;
-
-public:
-    static void process(AstNetlist* nodep) { OrderClkMarkVisitor{nodep}; }
-};
-
 //######################################################################
 // Order information stored under each AstNode::user1p()...
 
@@ -322,24 +126,15 @@ private:
 
 public:
     // METHODS
-    OrderVarVertex* getVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varscp,
-                                 VarVertexType type) {
+    OrderVarVertex* getVarVertex(OrderGraph* graphp, AstVarScope* varscp, VarVertexType type) {
         const unsigned idx = static_cast(type);
         OrderVarVertex* vertexp = m_vertexps[idx];
         if (!vertexp) {
             switch (type) {
-            case VarVertexType::STD:
-                vertexp = new OrderVarStdVertex(graphp, scopep, varscp);
-                break;
-            case VarVertexType::PRE:
-                vertexp = new OrderVarPreVertex(graphp, scopep, varscp);
-                break;
-            case VarVertexType::PORD:
-                vertexp = new OrderVarPordVertex(graphp, scopep, varscp);
-                break;
-            case VarVertexType::POST:
-                vertexp = new OrderVarPostVertex(graphp, scopep, varscp);
-                break;
+            case VarVertexType::STD: vertexp = new OrderVarStdVertex{graphp, varscp}; break;
+            case VarVertexType::PRE: vertexp = new OrderVarPreVertex{graphp, varscp}; break;
+            case VarVertexType::PORD: vertexp = new OrderVarPordVertex{graphp, varscp}; break;
+            case VarVertexType::POST: vertexp = new OrderVarPostVertex{graphp, varscp}; break;
             }
             m_vertexps[idx] = vertexp;
         }
@@ -363,35 +158,30 @@ class OrderBuildVisitor final : public VNVisitor {
     // NODE STATE
     //  AstVarScope::user1    -> OrderUser instance for variable (via m_orderUser)
     //  AstVarScope::user2    -> VarUsage within logic blocks
+    //  AstVarScope::user3    -> bool: Hybrid sensitivity
     const VNUser1InUse user1InUse;
     const VNUser2InUse user2InUse;
+    const VNUser3InUse user3InUse;
     AstUser1Allocator m_orderUser;
 
     // STATE
-    // The ordering graph built by this visitor
-    OrderGraph* const m_graphp = new OrderGraph;
-    // Singleton vertex that all top level inputs depend on
-    OrderInputsVertex* const m_inputsVxp = new OrderInputsVertex{m_graphp, nullptr};
-    // Singleton DPI Export trigger event vertex
-    OrderVarVertex* const m_dpiExportTriggerVxp
-        = v3Global.rootp()->dpiExportTriggerp()
-              ? getVarVertex(v3Global.rootp()->dpiExportTriggerp(), VarVertexType::STD)
-              : nullptr;
+    OrderGraph* const m_graphp = new OrderGraph;  // The ordering graph built by this visitor
+    OrderLogicVertex* m_logicVxp = nullptr;  // Current logic block being analyzed
 
-    OrderLogicVertex* m_activeSenVxp = nullptr;  // Sensitivity vertex for clocked logic
-    OrderLogicVertex* m_logicVxp = nullptr;  // Current loic block being analyzed
+    // Map from Trigger reference AstSenItem to the original AstSenTree
+    const std::unordered_map& m_trigToSen;
 
     // Current AstScope being processed
     AstScope* m_scopep = nullptr;
-    // Sensitivity list for non-combinational logic (incl. initial and settle),
-    // nullptr for combinational logic
+    // Sensitivity list for clocked logic, nullptr for combinational and hybird logic
     AstSenTree* m_domainp = nullptr;
+    // Sensitivity list for hybrid logic, nullptr for everything else
+    AstSenTree* m_hybridp = nullptr;
 
     bool m_inClocked = false;  // Underneath clocked AstActive
-    bool m_inClkAss = false;  // Underneath AstNodeAssign to clock
     bool m_inPre = false;  // Underneath AstAssignPre
     bool m_inPost = false;  // Underneath AstAssignPost/AstAlwaysPost
-    bool m_inPostponed = false;  // Underneath AstAlwaysPostponed
+    std::function m_readTriggersCombLogic;
 
     // METHODS
 
@@ -400,10 +190,7 @@ class OrderBuildVisitor final : public VNVisitor {
         // Reset VarUsage
         AstNode::user2ClearTree();
         // Create LogicVertex for this logic node
-        m_logicVxp = new OrderLogicVertex(m_graphp, m_scopep, m_domainp, nodep);
-        // If this logic has a clocked activation, add a link from the sensitivity list LogicVertex
-        // to this LogicVertex.
-        if (m_activeSenVxp) new OrderEdge(m_graphp, m_activeSenVxp, m_logicVxp, WEIGHT_NORMAL);
+        m_logicVxp = new OrderLogicVertex{m_graphp, m_scopep, m_domainp, m_hybridp, nodep};
         // Gather variable dependencies based on usage
         iterateChildren(nodep);
         // Finished with this logic
@@ -411,259 +198,184 @@ class OrderBuildVisitor final : public VNVisitor {
     }
 
     OrderVarVertex* getVarVertex(AstVarScope* varscp, VarVertexType type) {
-        return m_orderUser(varscp).getVarVertex(m_graphp, m_scopep, varscp, type);
+        return m_orderUser(varscp).getVarVertex(m_graphp, varscp, type);
     }
 
     // VISITORS
-    void visit(AstSenTree* nodep) override {
-        // This should only find the global AstSenTrees under the AstTopScope, which we ignore
-        // here. We visit AstSenTrees separately when encountering the AstActive that references
-        // them.
-        UASSERT_OBJ(!m_scopep, nodep, "AstSenTrees should have been made global in V3ActiveTop");
-    }
-    void visit(AstScope* nodep) override {
-        UASSERT_OBJ(!m_scopep, nodep, "Should not nest");
-        m_scopep = nodep;
-        iterateChildren(nodep);
-        m_scopep = nullptr;
-    }
     void visit(AstActive* nodep) override {
         UASSERT_OBJ(!nodep->sensesStorep(), nodep,
                     "AstSenTrees should have been made global in V3ActiveTop");
         UASSERT_OBJ(m_scopep, nodep, "AstActive not under AstScope");
         UASSERT_OBJ(!m_logicVxp, nodep, "AstActive under logic");
-        UASSERT_OBJ(!m_inClocked && !m_activeSenVxp && !m_domainp, nodep, "Should not nest");
+        UASSERT_OBJ(!m_inClocked && !m_domainp && !m_hybridp, nodep, "Should not nest");
 
-        m_inClocked = nodep->sensesp()->hasClocked();
+        // This is the original sensitivity of the block (i.e.: not the ref into the TRIGGERVEC)
 
-        // Analyze variable references in sensitivity list. Note that non-clocked sensitivity lists
-        // don't reference any variables (have no clocks), so the sensitivity list vertex would
-        // have no incoming dependencies and is hence redundant, therefore we only do this for
-        // clocked sensitivity lists.
-        if (m_inClocked) {
-            // Add LogicVertex for the sensitivity list of this AstActive.
-            m_activeSenVxp = new OrderLogicVertex(m_graphp, m_scopep, nodep->sensesp(), nodep);
-            // Analyze variables in the sensitivity list
-            iterateChildren(nodep->sensesp());
+        const AstSenTree* const senTreep = nodep->sensesp()->hasCombo()
+                                               ? nodep->sensesp()
+                                               : m_trigToSen.at(nodep->sensesp()->sensesp());
+
+        m_inClocked = senTreep->hasClocked();
+
+        // Note: We don't need to analyse the sensitivity list, as currently all sensitivity
+        // lists simply reference an entry in a trigger vector, which are all set external to
+        // the code being ordered.
+
+        // Combinational and hybrid logic will have it's domain assigned based on the driver
+        // domains. For clocked logic, we already know its domain.
+        if (!senTreep->hasCombo() && !senTreep->hasHybrid()) m_domainp = nodep->sensesp();
+
+        // Hybrid logic also includes additional sensitivities
+        if (senTreep->hasHybrid()) {
+            m_hybridp = nodep->sensesp();
+            // Mark AstVarScopes that are explicit sensitivities
+            AstNode::user3ClearTree();
+            senTreep->foreach([](const AstVarRef* refp) {  //
+                refp->varScopep()->user3(true);
+            });
+            m_readTriggersCombLogic = [](const AstVarScope* vscp) { return !vscp->user3(); };
+        } else {
+            // Always triggers
+            m_readTriggersCombLogic = [](const AstVarScope*) { return true; };
         }
 
-        // Ignore the sensitivity domain for combinational logic. We will assign combinational
-        // logic to a domain later, based on the domains of incoming variables.
-        if (!nodep->sensesp()->hasCombo()) m_domainp = nodep->sensesp();
-
         // Analyze logic underneath
         iterateChildren(nodep);
 
         //
         m_inClocked = false;
-        m_activeSenVxp = nullptr;
         m_domainp = nullptr;
+        m_hybridp = nullptr;
     }
     void visit(AstNodeVarRef* nodep) override {
         // As we explicitly not visit (see ignored nodes below) any subtree that is not relevant
         // for ordering, we should be able to assert this:
         UASSERT_OBJ(m_scopep, nodep, "AstVarRef not under scope");
-        UASSERT_OBJ(m_logicVxp || m_activeSenVxp, nodep,
-                    "AstVarRef not under logic nor sensitivity list");
+        UASSERT_OBJ(m_logicVxp, nodep, "AstVarRef not under logic");
         AstVarScope* const varscp = nodep->varScopep();
         UASSERT_OBJ(varscp, nodep, "Var didn't get varscoped in V3Scope.cpp");
-        if (!m_logicVxp) {
-            // Variable reference in sensitivity list. Add clock dependency.
 
-            UASSERT_OBJ(!nodep->access().isWriteOrRW(), nodep,
-                        "How can a sensitivity list be writing a variable?");
-            // Add edge from sensed VarStdVertex -> to sensitivity list LogicVertex
-            OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
-            varVxp->isClock(true);
-            new OrderEdge(m_graphp, varVxp, m_activeSenVxp, WEIGHT_MEDIUM);
-        } else {
-            // Variable reference in logic. Add data dependency.
+        // Variable reference in logic. Add data dependency.
 
-            // Check whether this variable was already generated/consumed in the same logic. We
-            // don't want to add extra edges if the logic has many usages of the same variable,
-            // so only proceed on first encounter.
-            const bool prevGen = varscp->user2() & VU_GEN;
-            const bool prevCon = varscp->user2() & VU_CON;
+        // Check whether this variable was already generated/consumed in the same logic. We
+        // don't want to add extra edges if the logic has many usages of the same variable,
+        // so only proceed on first encounter.
+        const bool prevGen = varscp->user2() & VU_GEN;
+        const bool prevCon = varscp->user2() & VU_CON;
 
-            // Compute whether the variable is produced (written) here
-            bool gen = false;
-            if (!prevGen && nodep->access().isWriteOrRW()) {
-                gen = true;
-                if (m_inPostponed) {
-                    // IEE 1800-2017 (4.2.9) forbids any value updates in the postponed region, but
-                    // Verilator generated trigger signals for $strobe are cleared after the
-                    // display is executed. This is both safe to ignore (because their single read
-                    // is in the same AstAlwaysPostponed, just prior to the clear), and is
-                    // necessary to ignore to avoid a circular logic (UNOPTFLAT) warning.
-                    UASSERT_OBJ(prevCon, nodep, "Should have been consumed in same process");
-                    gen = false;
-                }
+        // Compute whether the variable is produced (written) here
+        bool gen = !prevGen && nodep->access().isWriteOrRW();
+
+        // Compute whether the value is consumed (read) here
+        bool con = false;
+        if (!prevCon && nodep->access().isReadOrRW()) {
+            con = true;
+            if (prevGen && !m_inClocked) {
+                // Dangerous assumption:
+                // If a variable is consumed in the same combinational process that produced it
+                // earlier, consider it something like:
+                //      foo = 1
+                //      foo = foo + 1
+                // and still optimize. Note this will break though:
+                //      if (sometimes) foo = 1
+                //      foo = foo + 1
+                // TODO: Do this properly with liveness analysis (i.e.: if live, it's consumed)
+                //       Note however that this construct is not nicely synthesizable (yields
+                //       latch?).
+                con = false;
             }
+        }
 
-            // Compute whether the value is consumed (read) here
-            bool con = false;
-            if (!prevCon && nodep->access().isReadOrRW()) {
-                con = true;
-                if (prevGen && !m_inClocked) {
-                    // Dangerous assumption:
-                    // If a variable is consumed in the same combinational process that produced it
-                    // earlier, consider it something like:
-                    //      foo = 1
-                    //      foo = foo + 1
-                    // and still optimize. Note this will break though:
-                    //      if (sometimes) foo = 1
-                    //      foo = foo + 1
-                    // TODO: Do this properly with liveness analysis (i.e.: if live, it's consumed)
-                    //       Note however that this construct is not nicely synthesizable (yields
-                    //       latch?).
-                    con = false;
-                }
+        // Note: See V3OrderGraph.h about the roles of the various vertex types
 
-                // TODO: Explain how the following two exclusions are useful
-                if (varscp->varp()->attrClockEn() && !m_inPre && !m_inPost && !m_inClocked) {
-                    // 'clock_enable' attribute on this signal: user's worrying about it for us
-                    con = false;
-                }
-                if (m_inClkAss
-                    && (varscp->varp()->attrClocker() != VVarAttrClocker::CLOCKER_YES)) {
-                    // 'clocker' attribute on some other signal in same logic: same as
-                    // 'clock_enable' attribute above
-                    con = false;
-                    UINFO(4, "nodep used as clock_enable " << varscp << " in "
-                                                           << m_logicVxp->nodep() << endl);
-                }
-            }
-
-            // Roles of vertices:
-            // VarVertexType::STD:  Data dependencies for combinational logic and delayed
-            //                      assignment updates (AssignPost).
-            // VarVertexType::POST: Ensures all sequential blocks reading a signal do so before
-            //                      any combinational or delayed assignments update that signal.
-            // VarVertexType::PORD: Ensures a _d = _q AssignPre is the first write of a _d,
-            //                      before any sequential blocks write to that _d.
-            // VarVertexType::PRE:  This is an optimization. Try to ensure that a _d = _q
-            //                      AssignPre is the last read of a _q, after all reads of that
-            //                      _q by sequential logic. Note: The model is still correct if we
-            //                      cannot satisfy this due to other constraints. If this ordering
-            //                      is possible, then combined with the PORD constraint we get
-            //                      that all writes to _d are after all reads of a _q, which then
-            //                      allows us to eliminate the _d completely and assign to the _q
-            //                      directly (this is what V3LifePost does).
-
-            // Variable is produced
-            if (gen) {
-                // Update VarUsage
-                varscp->user2(varscp->user2() | VU_GEN);
-                // Add edges for produced variables
-                if (!m_inClocked || m_inPost) {
-                    // Combinational logic
-                    OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
-                    // Add edge from producing LogicVertex -> produced VarStdVertex
-                    if (m_inPost) {
-                        new OrderPostCutEdge(m_graphp, m_logicVxp, varVxp);
-                        // Mark the VarVertex as being produced by a delayed (non-blocking)
-                        // assignment. This is used to control marking internal clocks
-                        // circular, which must only happen if they are generated by delayed
-                        // assignment.
-                        varVxp->isDelayed(true);
-                        UINFO(5, "     Found delayed assignment (post) " << varVxp << endl);
-                    } else if (varscp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) {
-                        // If the variable has the 'clocker' attribute, avoid making it
-                        // circular by adding a hard edge instead of normal cuttable edge.
-                        new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL);
-                    } else {
-                        new OrderComboCutEdge(m_graphp, m_logicVxp, varVxp);
-                    }
-
-                    // Add edge from produced VarPostVertex -> to producing LogicVertex
-
-                    // For m_inPost:
-                    //    Add edge consumed_var_POST->logic_vertex
-                    //    This prevents a consumer of the "early" value to be scheduled
-                    //   after we've changed to the next-cycle value
-                    // ALWAYS do it:
-                    //    There maybe a wire a=b; between the two blocks
-                    OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST);
-                    new OrderEdge(m_graphp, postVxp, m_logicVxp, WEIGHT_POST);
-                } else if (m_inPre) {  // AstAssignPre
-                    // Add edge from producing LogicVertex -> produced VarPordVertex
-                    OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD);
-                    new OrderEdge(m_graphp, m_logicVxp, ordVxp, WEIGHT_NORMAL);
-                    // Add edge from producing LogicVertex -> produced VarStdVertex
-                    OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
-                    new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL);
+        // Variable is produced
+        if (gen) {
+            // Update VarUsage
+            varscp->user2(varscp->user2() | VU_GEN);
+            // Add edges for produced variables
+            if (!m_inClocked || m_inPost) {
+                // Combinational logic
+                OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
+                // Add edge from producing LogicVertex -> produced VarStdVertex
+                if (m_inPost) {
+                    m_graphp->addSoftEdge(m_logicVxp, varVxp, WEIGHT_COMBO);
                 } else {
-                    // Sequential (clocked) logic
-                    // Add edge from produced VarPordVertex -> to producing LogicVertex
-                    OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD);
-                    new OrderEdge(m_graphp, ordVxp, m_logicVxp, WEIGHT_NORMAL);
-                    // Add edge from producing LogicVertex-> to produced VarStdVertex
-                    OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
-                    new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL);
+                    m_graphp->addHardEdge(m_logicVxp, varVxp, WEIGHT_NORMAL);
                 }
-            }
 
-            // Variable is consumed
-            if (con) {
-                // Update VarUsage
-                varscp->user2(varscp->user2() | VU_CON);
-                // Add edges
-                if (!m_inClocked || m_inPost) {
-                    // Combinational logic
+                // Add edge from produced VarPostVertex -> to producing LogicVertex
+
+                // For m_inPost:
+                //    Add edge consumed_var_POST->logic_vertex
+                //    This prevents a consumer of the "early" value to be scheduled
+                //   after we've changed to the next-cycle value
+                // ALWAYS do it:
+                //    There maybe a wire a=b; between the two blocks
+                OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST);
+                m_graphp->addHardEdge(postVxp, m_logicVxp, WEIGHT_POST);
+            } else if (m_inPre) {  // AstAssignPre
+                // Add edge from producing LogicVertex -> produced VarPordVertex
+                OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD);
+                m_graphp->addHardEdge(m_logicVxp, ordVxp, WEIGHT_NORMAL);
+                // Add edge from producing LogicVertex -> produced VarStdVertex
+                OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
+                m_graphp->addHardEdge(m_logicVxp, varVxp, WEIGHT_NORMAL);
+            } else {
+                // Sequential (clocked) logic
+                // Add edge from produced VarPordVertex -> to producing LogicVertex
+                OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD);
+                m_graphp->addHardEdge(ordVxp, m_logicVxp, WEIGHT_NORMAL);
+                // Add edge from producing LogicVertex-> to produced VarStdVertex
+                OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
+                m_graphp->addHardEdge(m_logicVxp, varVxp, WEIGHT_NORMAL);
+            }
+        }
+
+        // Variable is consumed
+        if (con) {
+            // Update VarUsage
+            varscp->user2(varscp->user2() | VU_CON);
+            // Add edges
+            if (!m_inClocked || m_inPost) {
+                // Combinational logic
+                if (m_readTriggersCombLogic(varscp)) {
+                    // Ignore explicit sensitivities
                     OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD);
                     // Add edge from consumed VarStdVertex -> to consuming LogicVertex
-                    new OrderEdge(m_graphp, varVxp, m_logicVxp, WEIGHT_MEDIUM);
-                } else if (m_inPre) {
-                    // AstAssignPre logic
-                    // Add edge from consumed VarPreVertex -> to consuming LogicVertex
-                    // This one is cutable (vs the producer) as there's only one such consumer,
-                    // but may be many producers
-                    OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE);
-                    new OrderPreCutEdge(m_graphp, preVxp, m_logicVxp);
-                } else {
-                    // Sequential (clocked) logic
-                    // Add edge from consuming LogicVertex -> to consumed VarPreVertex
-                    // Generation of 'pre' because we want to indicate it should be before
-                    // AstAssignPre
-                    OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE);
-                    new OrderEdge(m_graphp, m_logicVxp, preVxp, WEIGHT_NORMAL);
-                    // Add edge from consuming LogicVertex -> to consumed VarPostVertex
-                    OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST);
-                    new OrderEdge(m_graphp, m_logicVxp, postVxp, WEIGHT_POST);
+                    m_graphp->addHardEdge(varVxp, m_logicVxp, WEIGHT_MEDIUM);
                 }
+            } else if (m_inPre) {
+                // AstAssignPre logic
+                // Add edge from consumed VarPreVertex -> to consuming LogicVertex
+                // This one is cutable (vs the producer) as there's only one such consumer,
+                // but may be many producers
+                OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE);
+                m_graphp->addSoftEdge(preVxp, m_logicVxp, WEIGHT_PRE);
+            } else {
+                // Sequential (clocked) logic
+                // Add edge from consuming LogicVertex -> to consumed VarPreVertex
+                // Generation of 'pre' because we want to indicate it should be before
+                // AstAssignPre
+                OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE);
+                m_graphp->addHardEdge(m_logicVxp, preVxp, WEIGHT_NORMAL);
+                // Add edge from consuming LogicVertex -> to consumed VarPostVertex
+                OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST);
+                m_graphp->addHardEdge(m_logicVxp, postVxp, WEIGHT_POST);
             }
         }
     }
-    void visit(AstDpiExportUpdated* nodep) override {
-        // This is under a logic block (AstAlways) sensitive to a change in the DPI export trigger.
-        // We just need to add an edge to the enclosing logic vertex (the vertex for the
-        // AstAlways).
-        OrderVarVertex* const varVxp = getVarVertex(nodep->varScopep(), VarVertexType::STD);
-        new OrderComboCutEdge(m_graphp, m_logicVxp, varVxp);
-        // Only used for ordering, so we can get rid of it here
-        nodep->unlinkFrBack();
-        VL_DO_DANGLING(pushDeletep(nodep), nodep);
-    }
-    void visit(AstCCall* nodep) override {
-        // Calls to 'context' imported DPI function may call DPI exported functions
-        if (m_dpiExportTriggerVxp && nodep->funcp()->dpiImportWrapper()
-            && nodep->funcp()->dpiContext()) {
-            UASSERT_OBJ(m_logicVxp, nodep, "Call not under logic");
-            new OrderEdge(m_graphp, m_logicVxp, m_dpiExportTriggerVxp, WEIGHT_NORMAL);
-        }
-        iterateChildren(nodep);
-    }
+    void visit(AstCCall* nodep) override { iterateChildren(nodep); }
 
     //--- Logic akin to SystemVerilog Processes (AstNodeProcedure)
-    void visit(AstInitial* nodep) override {  //
-        iterateLogic(nodep);
-    }
+    void visit(AstInitial* nodep) override {  // LCOV_EXCL_START
+        nodep->v3fatalSrc("AstInitial should not need ordering");
+    }  // LCOV_EXCL_STOP
+    void visit(AstInitialStatic* nodep) override {  // LCOV_EXCL_START
+        nodep->v3fatalSrc("AstInitialStatic should not need ordering");
+    }  // LCOV_EXCL_STOP
     void visit(AstInitialAutomatic* nodep) override {  //
         iterateLogic(nodep);
     }
-    void visit(AstInitialStatic* nodep) override {  //
-        iterateLogic(nodep);
-    }
     void visit(AstAlways* nodep) override {  //
         iterateLogic(nodep);
     }
@@ -673,41 +385,26 @@ class OrderBuildVisitor final : public VNVisitor {
         iterateLogic(nodep);
         m_inPost = false;
     }
-    void visit(AstAlwaysPostponed* nodep) override {
-        UASSERT_OBJ(!m_inPostponed, nodep, "Should not nest");
-        m_inPostponed = true;
-        iterateLogic(nodep);
-        m_inPostponed = false;
-    }
     void visit(AstFinal* nodep) override {  // LCOV_EXCL_START
-        nodep->v3fatalSrc("AstFinal should have been removed already");
+        nodep->v3fatalSrc("AstFinal should not need ordering");
     }  // LCOV_EXCL_STOP
 
     //--- Logic akin go SystemVerilog continuous assignments
     void visit(AstAssignAlias* nodep) override {  //
         iterateLogic(nodep);
     }
-    void visit(AstAssignW* nodep) override {
-        UASSERT_OBJ(!m_inClkAss, nodep, "Should not nest");
-        m_inClkAss = isClockerAssignment(nodep);
-        iterateLogic(nodep);
-        m_inClkAss = false;
-    }
+    void visit(AstAssignW* nodep) override { iterateLogic(nodep); }
     void visit(AstAssignPre* nodep) override {
-        UASSERT_OBJ(!m_inClkAss && !m_inPre, nodep, "Should not nest");
-        m_inClkAss = isClockerAssignment(nodep);
+        UASSERT_OBJ(!m_inPre, nodep, "Should not nest");
         m_inPre = true;
         iterateLogic(nodep);
         m_inPre = false;
-        m_inClkAss = false;
     }
     void visit(AstAssignPost* nodep) override {
-        UASSERT_OBJ(!m_inClkAss && !m_inPost, nodep, "Should not nest");
-        m_inClkAss = isClockerAssignment(nodep);
+        UASSERT_OBJ(!m_inPost, nodep, "Should not nest");
         m_inPost = true;
         iterateLogic(nodep);
         m_inPost = false;
-        m_inClkAss = false;
     }
 
     //--- Verilator concoctions
@@ -736,26 +433,27 @@ class OrderBuildVisitor final : public VNVisitor {
     void visit(AstNode* nodep) override { iterateChildren(nodep); }
 
     // CONSTRUCTOR
-    explicit OrderBuildVisitor(AstNetlist* nodep) {
-        // Add edges from the InputVertex to all top level input signal VarStdVertex
-        for (AstVarScope* vscp = nodep->topScopep()->scopep()->varsp(); vscp;
-             vscp = VN_AS(vscp->nextp(), VarScope)) {
-            if (vscp->varp()->isNonOutput()) {
-                OrderVarVertex* const varVxp = getVarVertex(vscp, VarVertexType::STD);
-                new OrderEdge(m_graphp, m_inputsVxp, varVxp, WEIGHT_INPUT);
+    OrderBuildVisitor(AstNetlist* /*nodep*/, const std::vector& coll,
+                      const std::unordered_map& trigToSen)
+        : m_trigToSen{trigToSen} {
+        // Build the graph
+        for (const V3Sched::LogicByScope* const lbsp : coll) {
+            for (const auto& pair : *lbsp) {
+                m_scopep = pair.first;
+                iterate(pair.second);
+                m_scopep = nullptr;
             }
         }
-
-        // Build the rest of the graph
-        iterate(nodep);
     }
     ~OrderBuildVisitor() override = default;
 
 public:
     // Process the netlist and return the constructed ordering graph. It's 'process' because
     // this visitor does change the tree (removes some nodes related to DPI export trigger).
-    static std::unique_ptr process(AstNetlist* nodep) {
-        return std::unique_ptr{OrderBuildVisitor{nodep}.m_graphp};
+    static std::unique_ptr
+    process(AstNetlist* nodep, const std::vector& coll,
+            const std::unordered_map& trigToSen) {
+        return std::unique_ptr{OrderBuildVisitor{nodep, coll, trigToSen}.m_graphp};
     }
 };
 
@@ -822,10 +520,9 @@ std::ostream& operator<<(std::ostream& lhs, const OrderMoveDomScope& rhs) {
 
 template 
 class ProcessMoveBuildGraph final {
-    // ProcessMoveBuildGraph takes as input the fine-grained graph of
-    // OrderLogicVertex, OrderVarVertex, etc; this is 'm_graph' in
-    // OrderVisitor. It produces a slightly coarsened graph to drive the
-    // code scheduling.
+    // ProcessMoveBuildGraph takes as input the fine-grained bipartite OrderGraph of
+    // OrderLogicVertex and OrderVarVertex vertices. It produces a slightly coarsened graph to
+    // drive the code scheduling.
     //
     // * For the serial code scheduler, the new graph contains
     //   nodes of type OrderMoveVertex.
@@ -837,9 +534,11 @@ class ProcessMoveBuildGraph final {
     //   'T_MoveVertex' template parameter; ProcessMoveBuildGraph otherwise
     //   works the same way for both cases.
 
+    // NODE STATE
+    // AstSenTree::user1p()     -> AstSenTree:  Original AstSenTree for trigger
+
     // TYPES
-    using VxDomPair = std::pair;
-    using Logic2Move = std::unordered_map;
+    using DomainMap = std::map;
 
 public:
     class MoveVertexMaker VL_NOT_FINAL {
@@ -847,30 +546,32 @@ public:
         // Clients of ProcessMoveBuildGraph must supply MoveVertexMaker
         // which creates new T_MoveVertex's. Each new vertex wraps lvertexp
         // (which may be nullptr.)
-        virtual T_MoveVertex* makeVertexp(  //
-            OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp,
-            const AstScope* scopep, const AstSenTree* domainp)
+        virtual T_MoveVertex* makeVertexp(OrderLogicVertex* lvertexp,
+                                          const OrderEitherVertex* varVertexp,
+                                          const AstSenTree* domainp)
             = 0;
-        virtual void freeVertexp(T_MoveVertex* freeMep) = 0;
     };
 
 private:
     // MEMBERS
-    const V3Graph* m_graphp;  // Input graph of OrderLogicVertex's etc
-    V3Graph* m_outGraphp;  // Output graph of T_MoveVertex's
+    const OrderGraph* const m_graphp;  // Input OrderGraph
+    V3Graph* const m_outGraphp;  // Output graph of T_MoveVertex vertices
+    // Map from Trigger reference AstSenItem to the original AstSenTree
+    const std::unordered_map& m_trigToSen;
     MoveVertexMaker* const m_vxMakerp;  // Factory class for T_MoveVertex's
-    Logic2Move m_logic2move;  // Map Logic to Vertex
-    // Maps an (original graph vertex, domain) pair to a T_MoveVertex
-    // Not std::unordered_map, because std::pair doesn't provide std::hash
-    std::map m_var2move;
+    // Storage for domain -> T_MoveVertex, maps held in OrderVarVertex::userp()
+    std::deque m_domainMaps;
 
 public:
     // CONSTRUCTORS
-    ProcessMoveBuildGraph(const V3Graph* logicGraphp,  // Input graph of OrderLogicVertex etc.
-                          V3Graph* outGraphp,  // Output graph of T_MoveVertex's
-                          MoveVertexMaker* vxMakerp)
+    ProcessMoveBuildGraph(
+        const OrderGraph* logicGraphp,  // Input graph of OrderLogicVertex etc.
+        V3Graph* outGraphp,  // Output graph of T_MoveVertex's
+        const std::unordered_map& trigToSen,
+        MoveVertexMaker* vxMakerp)
         : m_graphp{logicGraphp}
         , m_outGraphp{outGraphp}
+        , m_trigToSen{trigToSen}
         , m_vxMakerp{vxMakerp} {}
     virtual ~ProcessMoveBuildGraph() = default;
 
@@ -888,83 +589,139 @@ public:
         //      already created that pair, in which case, we've already
         //      done the forward search, so stop.
 
-        // For each logic node, make a T_MoveVertex
+        // For each logic vertex, make a T_MoveVertex, for each variable vertex, allocate storage
         for (V3GraphVertex* itp = m_graphp->verticesBeginp(); itp; itp = itp->verticesNextp()) {
-            if (OrderLogicVertex* const lvertexp = dynamic_cast(itp)) {
-                T_MoveVertex* const moveVxp = m_vxMakerp->makeVertexp(
-                    lvertexp, nullptr, lvertexp->scopep(), lvertexp->domainp());
-                if (moveVxp) {
-                    // Cross link so we can find it later
-                    m_logic2move[lvertexp] = moveVxp;
-                }
+            if (OrderLogicVertex* const lvtxp = dynamic_cast(itp)) {
+                lvtxp->userp(m_vxMakerp->makeVertexp(lvtxp, nullptr, lvtxp->domainp()));
+            } else {
+                // This is an OrderVarVertex
+                m_domainMaps.emplace_back();
+                itp->userp(&m_domainMaps.back());
             }
         }
         // Build edges between logic vertices
         for (V3GraphVertex* itp = m_graphp->verticesBeginp(); itp; itp = itp->verticesNextp()) {
-            if (OrderLogicVertex* const lvertexp = dynamic_cast(itp)) {
-                T_MoveVertex* const moveVxp = m_logic2move[lvertexp];
-                if (moveVxp) iterate(moveVxp, lvertexp, lvertexp->domainp());
+            if (OrderLogicVertex* const lvtxp = dynamic_cast(itp)) {
+                iterateLogicVertex(lvtxp);
             }
         }
     }
 
 private:
-    // Return true if moveVxp has downstream dependencies
-    bool iterate(T_MoveVertex* moveVxp, const V3GraphVertex* origVxp, const AstSenTree* domainp) {
-        bool madeDeps = false;
-        // Search forward from given original vertex, making new edges from
-        // moveVxp forward
-        for (V3GraphEdge* edgep = origVxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
-            if (edgep->weight() == 0) {  // Was cut
-                continue;
-            }
-            const int weight = edgep->weight();
-            if (const OrderLogicVertex* const toLVertexp
-                = dynamic_cast(edgep->top())) {
+    // Returns the AstSenItem that originally corresponds to this AstSenTree, or nullptr if no
+    // original AstSenTree, or if the original AstSenTree had multiple AstSenItems.
+    const AstSenItem* getOrigSenItem(AstSenTree* senTreep) {
+        if (!senTreep->user1p()) {
+            // Find the original simple AstSenTree, if any
+            AstNode* const origp = [&]() -> AstSenItem* {
+                // If more than one AstSenItems, then not a simple AstSenTree
+                if (senTreep->sensesp()->nextp()) return nullptr;
 
-                // Do not construct dependencies across exclusive domains.
-                if (domainsExclusive(domainp, toLVertexp->domainp())) continue;
+                // Find the original AstSenTree
+                auto it = m_trigToSen.find(senTreep->sensesp());
+                if (it == m_trigToSen.end()) return nullptr;
 
-                // Path from vertexp to a logic vertex; new edge.
-                // Note we use the last edge's weight, not some function of
-                // multiple edges
-                new OrderEdge(m_outGraphp, moveVxp, m_logic2move[toLVertexp], weight);
-                madeDeps = true;
-            } else {
-                // This is an OrderVarVertex or other vertex representing
-                // data. (Could be var, settle, or input type vertex.)
-                const V3GraphVertex* nonLogicVxp = edgep->top();
-                const VxDomPair key(nonLogicVxp, domainp);
-                if (!m_var2move[key]) {
-                    const OrderEitherVertex* const eithp
-                        = dynamic_cast(nonLogicVxp);
-                    T_MoveVertex* const newMoveVxp
-                        = m_vxMakerp->makeVertexp(nullptr, eithp, eithp->scopep(), domainp);
-                    m_var2move[key] = newMoveVxp;
+                // If more than one AstSenItems on the original, then not a simple AstSenTree
+                if (it->second->sensesp()->nextp()) return nullptr;
 
-                    // Find downstream logics that depend on (var, domain)
-                    if (!iterate(newMoveVxp, edgep->top(), domainp)) {
-                        // No downstream dependencies, so remove this
-                        // intermediate vertex.
-                        m_var2move[key] = nullptr;
-                        m_vxMakerp->freeVertexp(newMoveVxp);
-                        continue;
-                    }
-                }
+                // Else we found it.
+                return it->second->sensesp();
+            }();
 
-                // Create incoming edge, from previous logic that writes
-                // this var, to the Vertex representing the (var,domain)
-                new OrderEdge(m_outGraphp, moveVxp, m_var2move[key], weight);
-                madeDeps = true;
-            }
+            // We use the node itself as a sentinel to denote 'no original node'
+            senTreep->user1p(origp ? origp : senTreep);
         }
-        return madeDeps;
+
+        return senTreep->user1p() == senTreep ? nullptr : VN_AS(senTreep->user1p(), SenItem);
     }
+
+    bool domainsExclusive(AstSenTree* fromp, AstSenTree* top) {
+        // Return 'true' if we can prove that both 'from' and 'to' cannot both
+        // be active on the same evaluation, or false if we can't prove this.
+        //
+        // This detects the case of 'always @(posedge clk)'
+        // and 'always @(negedge clk)' being exclusive.
+        //
+        // Are there any other cases we need to handle? Maybe not,
+        // because these are not exclusive:
+        //   always @(posedge A or posedge B)
+        //   always @(negedge A)
+        //
+        // ... unless you know more about A and B, which sounds hard.
+
+        const AstSenItem* const fromSenItemp = getOrigSenItem(fromp);
+        if (!fromSenItemp) return false;
+        const AstSenItem* const toSenItemp = getOrigSenItem(top);
+        if (!toSenItemp) return false;
+
+        const AstNodeVarRef* const fromVarrefp = fromSenItemp->varrefp();
+        if (!fromVarrefp) return false;
+        const AstNodeVarRef* const toVarrefp = toSenItemp->varrefp();
+        if (!toVarrefp) return false;
+
+        // We know nothing about the relationship between different clocks here,
+        // so only proceed if strictly the same clock.
+        if (fromVarrefp->varScopep() != toVarrefp->varScopep()) return false;
+
+        return fromSenItemp->edgeType().exclusiveEdge(toSenItemp->edgeType());
+    }
+
+    void iterateLogicVertex(const OrderLogicVertex* lvtxp) {
+        AstSenTree* const domainp = lvtxp->domainp();
+        T_MoveVertex* const lMoveVtxp = static_cast(lvtxp->userp());
+        // Search forward from lvtxp, making new edges from lMoveVtxp forward
+        for (V3GraphEdge* edgep = lvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+            if (edgep->weight() == 0) continue;  // Was cut
+
+            // OrderGraph is a bipartite graph, so we know it's an OrderVarVertex
+            const OrderVarVertex* const vvtxp = static_cast(edgep->top());
+
+            // Look up T_MoveVertex for this domain on this variable
+            DomainMap& mapp = *static_cast(vvtxp->userp());
+            const auto pair = mapp.emplace(domainp, nullptr);
+            // Reference to the mapped T_MoveVertex
+            T_MoveVertex*& vMoveVtxp = pair.first->second;
+
+            // On first encounter, visit downstream logic dependent on this (var, domain)
+            if (pair.second) vMoveVtxp = iterateVarVertex(vvtxp, domainp);
+
+            // If no downstream dependents from this variable, then there is no need to add this
+            // variable as a dependent.
+            if (!vMoveVtxp) continue;
+
+            // Add this (variable, domain) as dependent of the logic that writes it.
+            new V3GraphEdge{m_outGraphp, lMoveVtxp, vMoveVtxp, 1};
+        }
+    }
+
+    // Return the T_MoveVertex for this (var, domain) pair, iff it has downstream dependencies,
+    // otherwise return nullptr.
+    T_MoveVertex* iterateVarVertex(const OrderVarVertex* vvtxp, AstSenTree* domainp) {
+        T_MoveVertex* vMoveVtxp = nullptr;
+        // Search forward from vvtxp, making new edges from vMoveVtxp forward
+        for (V3GraphEdge* edgep = vvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+            if (edgep->weight() == 0) continue;  // Was cut
+
+            // OrderGraph is a bipartite graph, so we know it's an OrderLogicVertex
+            const OrderLogicVertex* const lvtxp
+                = static_cast(edgep->top());
+
+            // Do not construct dependencies across exclusive domains.
+            if (domainsExclusive(domainp, lvtxp->domainp())) continue;
+
+            // there is a path from this vvtx to a logic vertex. Add the new edge.
+            if (!vMoveVtxp) vMoveVtxp = m_vxMakerp->makeVertexp(nullptr, vvtxp, domainp);
+            T_MoveVertex* const lMoveVxp = static_cast(lvtxp->userp());
+            new V3GraphEdge{m_outGraphp, vMoveVtxp, lMoveVxp, 1};
+        }
+        return vMoveVtxp;
+    }
+
     VL_UNCOPYABLE(ProcessMoveBuildGraph);
 };
 
-//######################################################################
-// OrderMoveVertexMaker and related
+// ######################################################################
+//  OrderMoveVertexMaker and related
 
 class OrderMoveVertexMaker final : public ProcessMoveBuildGraph::MoveVertexMaker {
     // MEMBERS
@@ -978,16 +735,13 @@ public:
         , m_pomWaitingp{pomWaitingp} {}
     // METHODS
     OrderMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex*,
-                                 const AstScope* scopep, const AstSenTree* domainp) override {
+                                 const AstSenTree* domainp) override {
         OrderMoveVertex* const resultp = new OrderMoveVertex(m_pomGraphp, lvertexp);
+        AstScope* const scopep = lvertexp ? lvertexp->scopep() : nullptr;
         resultp->domScopep(OrderMoveDomScope::findCreate(domainp, scopep));
         resultp->m_pomWaitingE.pushBack(*m_pomWaitingp, resultp);
         return resultp;
     }
-    void freeVertexp(OrderMoveVertex* freeMep) override {
-        freeMep->m_pomWaitingE.unlink(*m_pomWaitingp, freeMep);
-        freeMep->unlinkDelete(m_pomGraphp);
-    }
 
 private:
     VL_UNCOPYABLE(OrderMoveVertexMaker);
@@ -1001,13 +755,9 @@ public:
     explicit OrderMTaskMoveVertexMaker(V3Graph* pomGraphp)
         : m_pomGraphp{pomGraphp} {}
     MTaskMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp,
-                                 const AstScope* scopep, const AstSenTree* domainp) override {
-        // Exclude initial/settle logic from the mtasks graph.
-        // We'll output time-zero logic separately.
-        if (domainp->hasInitial() || domainp->hasSettle()) return nullptr;
-        return new MTaskMoveVertex(m_pomGraphp, lvertexp, varVertexp, scopep, domainp);
+                                 const AstSenTree* domainp) override {
+        return new MTaskMoveVertex{m_pomGraphp, lvertexp, varVertexp, domainp};
     }
-    void freeVertexp(MTaskMoveVertex* freeMep) override { freeMep->unlinkDelete(m_pomGraphp); }
 
 private:
     VL_UNCOPYABLE(OrderMTaskMoveVertexMaker);
@@ -1017,7 +767,7 @@ class OrderVerticesByDomainThenScope final {
     PartPtrIdMap m_ids;
 
 public:
-    virtual bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
+    bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
         const MTaskMoveVertex* const l_vxp = static_cast(lhsp);
         const MTaskMoveVertex* const r_vxp = static_cast(rhsp);
         uint64_t l_id = m_ids.findId(l_vxp->domainp());
@@ -1030,14 +780,10 @@ public:
     }
 };
 
-class MTaskVxIdLessThan final {
-public:
-    MTaskVxIdLessThan() = default;
-    virtual ~MTaskVxIdLessThan() = default;
-
+struct MTaskVxIdLessThan final {
     // Sort vertex's, which must be AbstractMTask's, into a deterministic
     // order by comparing their serial IDs.
-    virtual bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
+    bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
         const AbstractMTask* const lmtaskp = static_cast(lhsp);
         const AbstractMTask* const rmtaskp = static_cast(rhsp);
         return lmtaskp->id() < rmtaskp->id();
@@ -1049,17 +795,24 @@ public:
 
 class OrderProcess final : VNDeleter {
     // NODE STATE
-    //  AstNode::user3  -> Used by loop reporting
     //  AstNode::user4  -> Used by V3Const::constifyExpensiveEdit
-    const VNUser3InUse user3InUse;
 
     // STATE
     OrderGraph& m_graph;  // The ordering graph
-    OrderInputsVertex& m_inputsVtx;  // The singleton OrderInputsVertex
+
+    // Map from Trigger reference AstSenItem to the original AstSenTree
+    const std::unordered_map& m_trigToSen;
+
+    // This is a function provided by the invoker of the ordering that can provide additional
+    // sensitivity expression that when triggered indicates the passed AstVarScope might have
+    // changed external to the code being ordered.
+    const V3Order::ExternalDomainsProvider m_externalDomains;
+
     SenTreeFinder m_finder;  // Global AstSenTree manager
-    AstSenTree* const m_comboDomainp;  // The combinational domain AstSenTree
     AstSenTree* const m_deleteDomainp;  // Dummy AstSenTree indicating needs deletion
-    AstScope& m_scopetop;  // The top level AstScope
+    const string m_tag;  // Subtring to add to generated names
+    const bool m_slow;  // Ordering slow code
+    std::vector m_result;  // The result nodes (~statements) in their sequential order
 
     AstCFunc* m_pomNewFuncp = nullptr;  // Current function being created
     int m_pomNewStmts = 0;  // Statements in function being created
@@ -1067,21 +820,11 @@ class OrderProcess final : VNDeleter {
     V3List m_pomWaiting;  // List of nodes needing inputs to become ready
     friend class OrderMoveDomScope;
     V3List m_pomReadyDomScope;  // List of ready domain/scope pairs, by loopId
-    std::vector m_unoptflatVars;  // Vector of variables in UNOPTFLAT loop
     std::map, unsigned> m_funcNums;  // Function ordinals
 
-    // STATS
-    std::array m_statCut;  // Count of each edge type cut
-
     // METHODS
 
-    void process();
-    void processCircular();
-    using VertexVec = std::deque;
-    void processInputs();
-    void processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec);
-    void processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec& todoVec);
-    void processSensitive();
+    void process(bool multiThreaded);
     void processDomains();
     void processDomainsIterate(OrderEitherVertex* vertexp);
     void processEdgeReport();
@@ -1105,16 +848,11 @@ class OrderProcess final : VNDeleter {
         MTaskState() = default;
     };
     void processMTasks();
-    enum InitialLogicE : uint8_t { LOGIC_INITIAL, LOGIC_SETTLE };
-    void processMTasksInitial(InitialLogicE logic_type);
 
     string cfuncName(AstNodeModule* modp, AstSenTree* domainp, AstScope* scopep,
                      AstNode* forWhatp) {
-        string name = domainp->hasCombo()     ? "_combo"
-                      : domainp->hasInitial() ? "_initial"
-                      : domainp->hasSettle()  ? "_settle"
-                      : domainp->isMulti()    ? "_multiclk"
-                                              : "_sequent";
+        string name = "_" + m_tag;
+        name += domainp->isMulti() ? "_comb" : "_sequent";
         name = name + "__" + scopep->nameDotless();
         const unsigned funcnum = m_funcNums.emplace(std::make_pair(modp, name), 0).first->second++;
         name = name + "__" + cvtToStr(funcnum);
@@ -1124,219 +862,51 @@ class OrderProcess final : VNDeleter {
         return name;
     }
 
-    bool nodeIsInitial(const OrderLogicVertex* LVtxp) {
-        return LVtxp && (VN_IS(LVtxp->nodep(), Initial) || VN_IS(LVtxp->nodep(), InitialStatic));
-    }
-
-    void nodeMarkCircular(OrderVarVertex* vertexp, OrderEdge* edgep) {
-        // To be marked circular requires being a clock assigned in a delayed assignment, or
-        // having a cutable in or out edge, none of which is true for the DPI export trigger.
-        AstVarScope* const nodep = vertexp->varScp();
-        UASSERT(nodep != v3Global.rootp()->dpiExportTriggerp(),
-                "DPI export trigger should not be marked circular");
-        const OrderLogicVertex* fromLVtxp = nullptr;
-        const OrderLogicVertex* toLVtxp = nullptr;
-        if (edgep) {
-            fromLVtxp = dynamic_cast(edgep->fromp());
-            toLVtxp = dynamic_cast(edgep->top());
-        }
-        //
-        if (nodeIsInitial(fromLVtxp) || nodeIsInitial(toLVtxp)) {
-            // IEEE does not specify ordering between initial blocks, so we
-            // can do whatever we want. We especially do not want to
-            // evaluate multiple times, so do not mark the edge circular
-        } else {
-            nodep->circular(true);
-            ++m_statCut[vertexp->type()];
-            if (edgep) ++m_statCut[edgep->type()];
-            //
-            if (vertexp->isClock()) {
-                // Seems obvious; no warning yet
-                // nodep->v3warn(GENCLK, "Signal unoptimizable: Generated clock:
-                // "<prettyNameQ());
-            } else if (nodep->varp()->isSigPublic()) {
-                nodep->v3warn(UNOPT,
-                              "Signal unoptimizable: Feedback to public clock or circular logic: "
-                                  << nodep->prettyNameQ());
-                if (!nodep->fileline()->warnIsOff(V3ErrorCode::UNOPT)
-                    && !nodep->fileline()->lastWarnWaived()) {
-                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNOPT,
-                                                     true);  // Complain just once
-                    // Give the user an example.
-                    const bool tempWeight = (edgep && edgep->weight() == 0);
-                    // Else the below loop detect can't see the loop
-                    if (tempWeight) edgep->weight(1);
-                    // Calls OrderGraph::loopsVertexCb
-                    m_graph.reportLoops(&OrderEdge::followComboConnected, vertexp);
-                    if (tempWeight) edgep->weight(0);
-                }
-            } else {
-                // We don't use UNOPT, as there are lots of V2 places where
-                // it was needed, that aren't any more
-                // First v3warn not inside warnIsOff so we can see the suppressions with --debug
-                nodep->v3warn(UNOPTFLAT,
-                              "Signal unoptimizable: Feedback to clock or circular logic: "
-                                  << nodep->prettyNameQ());
-                if (!nodep->fileline()->warnIsOff(V3ErrorCode::UNOPTFLAT)
-                    && !nodep->fileline()->lastWarnWaived()) {
-                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNOPTFLAT,
-                                                     true);  // Complain just once
-                    // Give the user an example.
-                    const bool tempWeight = (edgep && edgep->weight() == 0);
-                    // Else the below loop detect can't see the loop
-                    if (tempWeight) edgep->weight(1);
-                    // Calls OrderGraph::loopsVertexCb
-                    m_graph.reportLoops(&OrderEdge::followComboConnected, vertexp);
-                    if (tempWeight) edgep->weight(0);
-                    if (v3Global.opt.reportUnoptflat()) {
-                        // Report candidate variables for splitting
-                        reportLoopVars(vertexp);
-                        // Do a subgraph for the UNOPTFLAT loop
-                        OrderGraph loopGraph;
-                        m_graph.subtreeLoops(&OrderEdge::followComboConnected, vertexp,
-                                             &loopGraph);
-                        loopGraph.dumpDotFilePrefixedAlways("unoptflat");
-                    }
-                }
-            }
-        }
-    }
-
-    // Find all variables in an UNOPTFLAT loop
-    //
-    // Ignore vars that are 1-bit wide and don't worry about generated
-    // variables (PRE and POST vars, __Vdly__, __Vcellin__ and __VCellout).
-    // What remains are candidates for splitting to break loops.
-    //
-    // node->user3 is used to mark if we have done a particular variable.
-    // vertex->user is used to mark if we have seen this vertex before.
-    //
-    // @todo We could be cleverer in the future and consider just
-    //       the width that is generated/consumed.
-    void reportLoopVars(OrderVarVertex* vertexp) {
-        m_graph.userClearVertices();
-        AstNode::user3ClearTree();
-        m_unoptflatVars.clear();
-        reportLoopVarsIterate(vertexp, vertexp->color());
-        AstNode::user3ClearTree();
-        m_graph.userClearVertices();
-        // May be very large vector, so only report the "most important"
-        // elements. Up to 10 of the widest
-        std::cerr << V3Error::warnMore() << "... Widest candidate vars to split:\n";
-        std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(),
-                         [](OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) -> bool {
-                             return vsv1p->varScp()->varp()->width()
-                                    > vsv2p->varScp()->varp()->width();
-                         });
-        std::unordered_set canSplitList;
-        int lim = m_unoptflatVars.size() < 10 ? m_unoptflatVars.size() : 10;
-        for (int i = 0; i < lim; i++) {
-            OrderVarStdVertex* const vsvertexp = m_unoptflatVars[i];
-            AstVar* const varp = vsvertexp->varScp()->varp();
-            const bool canSplit = V3SplitVar::canSplitVar(varp);
-            std::cerr << V3Error::warnMore() << "    " << varp->fileline() << " "
-                      << varp->prettyName() << std::dec << ", width " << varp->width()
-                      << ", fanout " << vsvertexp->fanout();
-            if (canSplit) {
-                std::cerr << ", can split_var";
-                canSplitList.insert(varp);
-            }
-            std::cerr << '\n';
-        }
-        // Up to 10 of the most fanned out
-        std::cerr << V3Error::warnMore() << "... Most fanned out candidate vars to split:\n";
-        std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(),
-                         [](OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) -> bool {
-                             return vsv1p->fanout() > vsv2p->fanout();
-                         });
-        lim = m_unoptflatVars.size() < 10 ? m_unoptflatVars.size() : 10;
-        for (int i = 0; i < lim; i++) {
-            OrderVarStdVertex* const vsvertexp = m_unoptflatVars[i];
-            AstVar* const varp = vsvertexp->varScp()->varp();
-            const bool canSplit = V3SplitVar::canSplitVar(varp);
-            std::cerr << V3Error::warnMore() << "    " << varp->fileline() << " "
-                      << varp->prettyName() << ", width " << std::dec << varp->width()
-                      << ", fanout " << vsvertexp->fanout();
-            if (canSplit) {
-                std::cerr << ", can split_var";
-                canSplitList.insert(varp);
-            }
-            std::cerr << '\n';
-        }
-        if (!canSplitList.empty()) {
-            std::cerr << V3Error::warnMore()
-                      << "... Suggest add /*verilator split_var*/ to appropriate variables above."
-                      << std::endl;
-        }
-        V3Stats::addStat("Order, SplitVar, candidates", canSplitList.size());
-        m_unoptflatVars.clear();
-    }
-
-    void reportLoopVarsIterate(V3GraphVertex* vertexp, uint32_t color) {
-        if (vertexp->user()) return;  // Already done
-        vertexp->user(1);
-        if (OrderVarStdVertex* const vsvertexp = dynamic_cast(vertexp)) {
-            // Only reporting on standard variable vertices
-            AstVar* const varp = vsvertexp->varScp()->varp();
-            if (!varp->user3()) {
-                const string name = varp->prettyName();
-                if ((varp->width() != 1) && (name.find("__Vdly") == string::npos)
-                    && (name.find("__Vcell") == string::npos)) {
-                    // Variable to report on and not yet done
-                    m_unoptflatVars.push_back(vsvertexp);
-                }
-                varp->user3Inc();
-            }
-        }
-        // Iterate through all the to and from vertices of the same color
-        for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
-            if (edgep->top()->color() == color) reportLoopVarsIterate(edgep->top(), color);
-        }
-        for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
-            if (edgep->fromp()->color() == color) reportLoopVarsIterate(edgep->fromp(), color);
-        }
-    }
-
-    // Only for member initialization in constructor
-    static OrderInputsVertex& findInputVertex(OrderGraph& graph) {
-        for (V3GraphVertex* vtxp = graph.verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
-            if (auto* const ivtxp = dynamic_cast(vtxp)) return *ivtxp;
-        }
-        VL_UNREACHABLE
+    // Make a domain that merges the two domains
+    AstSenTree* combineDomains(AstSenTree* ap, AstSenTree* bp) {
+        if (ap == m_deleteDomainp) return bp;
+        UASSERT_OBJ(bp != m_deleteDomainp, bp, "Should not be delete domain");
+        AstSenTree* const senTreep = ap->cloneTree(false);
+        senTreep->addSensesp(bp->sensesp()->cloneTree(true));
+        V3Const::constifyExpensiveEdit(senTreep);  // Remove duplicates
+        senTreep->multi(true);  // Comment that it was made from 2 domains
+        AstSenTree* const resultp = m_finder.getSenTree(senTreep);
+        VL_DO_DANGLING(senTreep->deleteTree(), senTreep);  // getSenTree clones, so delete this
+        return resultp;
     }
 
     // Only for member initialization in constructor
     static AstSenTree* makeDeleteDomainSenTree(FileLine* fl) {
-        // TODO: Using "Never" instead of "Settle" causes a test failure, it probably shouldn't ...
-        return new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Settle{}}};
+        return new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Illegal{}}};
     }
 
     // CONSTRUCTOR
-    OrderProcess(AstNetlist* netlistp, OrderGraph& graph)
+    OrderProcess(AstNetlist* netlistp, OrderGraph& graph,
+                 const std::unordered_map& trigToSen,
+                 const string& tag, bool slow,
+                 const V3Order::ExternalDomainsProvider& externalDomains)
         : m_graph{graph}
-        , m_inputsVtx{findInputVertex(graph)}
+        , m_trigToSen{trigToSen}
+        , m_externalDomains{externalDomains}
         , m_finder{netlistp}
-        , m_comboDomainp{m_finder.getComb()}
         , m_deleteDomainp{makeDeleteDomainSenTree(netlistp->fileline())}
-        , m_scopetop{*netlistp->topScopep()->scopep()} {
+        , m_tag{tag}
+        , m_slow{slow} {
         pushDeletep(m_deleteDomainp);
     }
 
-    ~OrderProcess() override {
-        // Stats
-        for (int type = 0; type < OrderVEdgeType::_ENUM_END; type++) {
-            const double count{m_statCut[type]};
-            if (count != 0.0) {
-                V3Stats::addStat(std::string{"Order, cut, "} + OrderVEdgeType{type}.ascii(),
-                                 count);
-            }
-        }
-    }
+    ~OrderProcess() override = default;
 
 public:
     // Order the logic
-    static void main(AstNetlist* netlistp, OrderGraph& graph) {
-        OrderProcess{netlistp, graph}.process();
+    static std::vector
+    main(AstNetlist* netlistp, OrderGraph& graph,
+         const std::unordered_map& trigToSen,
+         const string& tag, bool parallel, bool slow,
+         const V3Order::ExternalDomainsProvider& externalDomains) {
+        OrderProcess visitor{netlistp, graph, trigToSen, tag, slow, externalDomains};
+        visitor.process(parallel);
+        return std::move(visitor.m_result);
     }
 };
 
@@ -1362,159 +932,6 @@ void OrderMoveDomScope::movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp)
 }
 
 //######################################################################
-// OrderProcess methods
-
-void OrderProcess::processInputs() {
-    m_graph.userClearVertices();  // Vertex::user()   // 1 if input recursed, 2 if marked as input,
-                                  // 3 if out-edges recursed
-    // Start at input vertex, process from input-to-output order
-    VertexVec todoVec;  // List of newly-input marked vectors we need to process
-    todoVec.push_front(&m_inputsVtx);
-    m_inputsVtx.isFromInput(true);  // By definition
-    while (!todoVec.empty()) {
-        OrderEitherVertex* const vertexp = todoVec.back();
-        todoVec.pop_back();
-        processInputsOutIterate(vertexp, todoVec);
-    }
-}
-
-void OrderProcess::processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) {
-    // Propagate PrimaryIn through simple assignments
-    if (vertexp->user()) return;  // Already processed
-    if (false && debug() >= 9) {
-        UINFO(9, " InIIter " << vertexp << endl);
-        if (OrderLogicVertex* const vvertexp = dynamic_cast(vertexp)) {
-            vvertexp->nodep()->dumpTree(cout, "-            TT: ");
-        }
-    }
-    vertexp->user(1);  // Processing
-    // First handle all inputs to this vertex, in most cases they'll be already processed earlier
-    // Also, determine if this vertex is an input
-    int inonly = 1;  // 0=no, 1=maybe, 2=yes until a no
-    for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
-        OrderEitherVertex* const frVertexp = static_cast(edgep->fromp());
-        processInputsInIterate(frVertexp, todoVec);
-        if (frVertexp->isFromInput()) {
-            if (inonly == 1) inonly = 2;
-        } else if (dynamic_cast(frVertexp)) {
-            // Ignore post assignments, just for ordering
-        } else {
-            // UINFO(9, "    InItStopDueTo " << frVertexp << endl);
-            inonly = 0;
-            break;
-        }
-    }
-
-    if (inonly == 2
-        && vertexp->user() < 2) {  // Set it.  Note may have already been set earlier, too
-        UINFO(9, "   Input reassignment: " << vertexp << endl);
-        vertexp->isFromInput(true);
-        vertexp->user(2);  // 2 means on list
-        // Can't work on out-edges of a node we know is an input immediately,
-        // as it might visit other nodes before their input state is resolved.
-        // So push to list and work on it later when all in-edges known resolved
-        todoVec.push_back(vertexp);
-    }
-    // UINFO(9, "  InIdone " << vertexp << endl);
-}
-
-void OrderProcess::processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) {
-    if (vertexp->user() == 3) return;  // Already out processed
-    // UINFO(9, " InOIter " << vertexp << endl);
-    // First make sure input path is fully recursed
-    processInputsInIterate(vertexp, todoVec);
-    // Propagate PrimaryIn through simple assignments
-    UASSERT_OBJ(vertexp->isFromInput(), vertexp,
-                "processInputsOutIterate only for input marked vertexes");
-    vertexp->user(3);  // out-edges processed
-
-    {
-        // Propagate PrimaryIn through simple assignments, following target of vertex
-        for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
-            OrderEitherVertex* const toVertexp = static_cast(edgep->top());
-            if (OrderVarStdVertex* const vvertexp = dynamic_cast(toVertexp)) {
-                processInputsInIterate(vvertexp, todoVec);
-            }
-            if (OrderLogicVertex* const vvertexp = dynamic_cast(toVertexp)) {
-                if (VN_IS(vvertexp->nodep(), NodeAssign)) {
-                    processInputsInIterate(vvertexp, todoVec);
-                }
-            }
-        }
-    }
-}
-
-//######################################################################
-// OrderVisitor - Circular detection
-
-void OrderProcess::processCircular() {
-    // Take broken edges and add circular flags
-    // The change detect code will use this to force changedets
-    for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
-        if (OrderVarStdVertex* const vvertexp = dynamic_cast(itp)) {
-            if (vvertexp->isClock() && !vvertexp->isFromInput()) {
-                // If a clock is generated internally, we need to do another
-                // loop through the entire evaluation.  This fixes races; see
-                // t_clk_dpulse test.
-                //
-                // This all seems to hinge on how the clock is generated. If
-                // it is generated by delayed assignment, we need the loop. If
-                // it is combinatorial, we do not (and indeed it will break
-                // other tests such as t_gated_clk_1.
-                if (!v3Global.opt.orderClockDly()) {
-                    UINFO(5, "Circular Clock, no-order-clock-delay " << vvertexp << endl);
-                    nodeMarkCircular(vvertexp, nullptr);
-                } else if (vvertexp->isDelayed()) {
-                    UINFO(5, "Circular Clock, delayed " << vvertexp << endl);
-                    nodeMarkCircular(vvertexp, nullptr);
-                } else {
-                    UINFO(5, "Circular Clock, not delayed " << vvertexp << endl);
-                }
-            }
-            // Also mark any cut edges
-            for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
-                if (edgep->weight() == 0) {  // was cut
-                    OrderEdge* const oedgep = dynamic_cast(edgep);
-                    UASSERT_OBJ(oedgep, vvertexp->varScp(), "Cutable edge not of proper type");
-                    UINFO(6, "      CutCircularO: " << vvertexp->name() << endl);
-                    nodeMarkCircular(vvertexp, oedgep);
-                }
-            }
-            for (V3GraphEdge* edgep = vvertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
-                if (edgep->weight() == 0) {  // was cut
-                    OrderEdge* const oedgep = dynamic_cast(edgep);
-                    UASSERT_OBJ(oedgep, vvertexp->varScp(), "Cutable edge not of proper type");
-                    UINFO(6, "      CutCircularI: " << vvertexp->name() << endl);
-                    nodeMarkCircular(vvertexp, oedgep);
-                }
-            }
-        }
-    }
-}
-
-void OrderProcess::processSensitive() {
-    // Sc sensitives are required on all inputs that go to a combo
-    // block.  (Not inputs that go only to clocked blocks.)
-    for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
-        if (OrderVarStdVertex* const vvertexp = dynamic_cast(itp)) {
-            if (vvertexp->varScp()->varp()->isNonOutput()) {
-                // UINFO(0, "  scsen " << vvertexp << endl);
-                for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep;
-                     edgep = edgep->outNextp()) {
-                    if (OrderEitherVertex* const toVertexp
-                        = dynamic_cast(edgep->top())) {
-                        if (edgep->weight() && toVertexp->domainp()) {
-                            // UINFO(0, "      " << toVertexp->domainp() << endl);
-                            if (toVertexp->domainp()->hasCombo()) {
-                                vvertexp->varScp()->varp()->scSensitive(true);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
 
 void OrderProcess::processDomains() {
     for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
@@ -1532,84 +949,55 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) {
     //     else, if all inputs are from flops, it's end-of-sequential code
     //     else, it's full combo code
     if (vertexp->domainp()) return;  // Already processed, or sequential logic
+
     UINFO(5, "    pdi: " << vertexp << endl);
-    OrderVarVertex* const vvertexp = dynamic_cast(vertexp);
     AstSenTree* domainp = nullptr;
-    if (vvertexp && vvertexp->varScp()->varp()->isNonOutput()) domainp = m_comboDomainp;
-    if (vvertexp && vvertexp->varScp()->isCircular()) domainp = m_comboDomainp;
-    if (!domainp) {
-        for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
-            OrderEitherVertex* const fromVertexp = static_cast(edgep->fromp());
-            if (edgep->weight() && fromVertexp->domainMatters()) {
-                UINFO(9, "     from d=" << cvtToHex(fromVertexp->domainp()) << " " << fromVertexp
-                                        << endl);
-                if (!domainp  // First input to this vertex
-                    || domainp->hasSettle()  // or, we can ignore being in the settle domain
-                    || domainp->hasInitial()) {
-                    domainp = fromVertexp->domainp();
-                } else if (domainp->hasCombo()) {
-                    // Once in combo, keep in combo; already as severe as we can get
-                } else if (fromVertexp->domainp()->hasCombo()) {
-                    // Any combo input means this vertex must remain combo
-                    domainp = m_comboDomainp;
-                } else if (fromVertexp->domainp()->hasSettle()
-                           || fromVertexp->domainp()->hasInitial()) {
-                    // Ignore that we have a constant (initial) input
-                } else if (domainp != fromVertexp->domainp()) {
-                    // Make a domain that merges the two domains
-                    const bool ddebug = debug() >= 9;
+    if (OrderLogicVertex* const lvtxp = dynamic_cast(vertexp)) {
+        domainp = lvtxp->hybridp();
+    }
 
-                    if (ddebug) {  // LCOV_EXCL_START
+    std::vector externalDomainps;
 
-                        cout << endl;
-                        UINFO(0, "      conflicting domain " << fromVertexp << endl);
-                        UINFO(0, "         dorig=" << domainp << endl);
-                        domainp->dumpTree(cout);
-                        UINFO(0, "         d2   =" << fromVertexp->domainp() << endl);
-                        fromVertexp->domainp()->dumpTree(cout);
-                    }  // LCOV_EXCL_STOP
-                    AstSenTree* const newtreep = domainp->cloneTree(false);
-                    AstSenItem* newtree2p = fromVertexp->domainp()->sensesp()->cloneTree(true);
-                    UASSERT_OBJ(newtree2p, fromVertexp->domainp(),
-                                "No senitem found under clocked domain");
-                    newtreep->addSensesp(newtree2p);
-                    newtree2p = nullptr;  // Below edit may replace it
-                    V3Const::constifyExpensiveEdit(newtreep);  // Remove duplicates
-                    newtreep->multi(true);  // Comment that it was made from 2 clock domains
-                    domainp = m_finder.getSenTree(newtreep);
-                    if (ddebug) {  // LCOV_EXCL_START
-                        UINFO(0, "         dnew =" << newtreep << endl);
-                        newtreep->dumpTree(cout);
-                        UINFO(0, "         find =" << domainp << endl);
-                        domainp->dumpTree(cout);
-                        cout << endl;
-                    }  // LCOV_EXCL_STOP
-                    VL_DO_DANGLING(newtreep->deleteTree(), newtreep);
+    for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+        OrderEitherVertex* const fromVertexp = static_cast(edgep->fromp());
+        if (edgep->weight() && fromVertexp->domainMatters()) {
+            AstSenTree* fromDomainp = fromVertexp->domainp();
+            UASSERT(!fromDomainp->hasCombo(), "There should be no need for combinational domains");
+
+            if (OrderVarVertex* const varVtxp = dynamic_cast(fromVertexp)) {
+                AstVarScope* const vscp = varVtxp->vscp();
+                // Add in any external domains
+                externalDomainps.clear();
+                m_externalDomains(vscp, externalDomainps);
+                for (AstSenTree* const externalDomainp : externalDomainps) {
+                    UASSERT_OBJ(!externalDomainp->hasCombo(), vscp,
+                                "There should be no need for combinational domains");
+                    fromDomainp = combineDomains(fromDomainp, externalDomainp);
                 }
             }
-        }  // next input edgep
-        // Default the domain
-        // This is a node which has only constant inputs, or is otherwise indeterminate.
-        // It should have already been copied into the settle domain.  Presumably it has
-        // inputs which we never trigger, or nothing it's sensitive to, so we can rip it out.
-        if (!domainp && vertexp->scopep()) domainp = m_deleteDomainp;
-        // However, anything that is public RW must be added to the combo domain since the
-        // user may change it at any time
-        if (domainp && vvertexp && vvertexp->varScp()->varp()->isSigUserRWPublic())
-            domainp = m_comboDomainp;
+
+            // Irrelevant input vertex (never triggered)
+            if (fromDomainp == m_deleteDomainp) continue;
+
+            // First input to this vertex
+            if (!domainp) domainp = fromDomainp;
+
+            // Make a domain that merges the two domains
+            if (domainp != fromDomainp) domainp = combineDomains(domainp, fromDomainp);
+        }
     }
-    //
+
+    // If nothing triggers this vertex, we can delete the corresponding logic
+    if (!domainp) domainp = m_deleteDomainp;
+
+    // Set the domain of the vertex
     vertexp->domainp(domainp);
-    if (vertexp->domainp()) {
-        UINFO(5, "      done d=" << cvtToHex(vertexp->domainp())
-                                 << (vertexp->domainp() == m_deleteDomainp ? " [DEL]" : "")
-                                 << (vertexp->domainp()->hasClocked() ? " [CLKD]" : "")
-                                 << (vertexp->domainp()->hasSettle() ? " [SETL]" : "")
-                                 << (vertexp->domainp()->hasInitial() ? " [INIT]" : "")
-                                 << (vertexp->domainp()->hasCombo() ? " [COMB]" : "")
-                                 << (vertexp->domainp()->isMulti() ? " [MULT]" : "") << " "
-                                 << vertexp << endl);
-    }
+    UINFO(5, "      done d=" << cvtToHex(vertexp->domainp())
+                             << (domainp == m_deleteDomainp       ? " [DEL]"
+                                 : vertexp->domainp()->hasCombo() ? " [COMB]"
+                                 : vertexp->domainp()->isMulti()  ? " [MULT]"
+                                                                  : "")
+                             << " " << vertexp << endl);
 }
 
 //######################################################################
@@ -1617,16 +1005,20 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) {
 
 void OrderProcess::processEdgeReport() {
     // Make report of all signal names and what clock edges they have
-    const string filename = v3Global.debugFilename("order_edges.txt");
+    const string filename = v3Global.debugFilename(m_tag + "_order_edges.txt");
     const std::unique_ptr logp{V3File::new_ofstream(filename)};
     if (logp->fail()) v3fatal("Can't write " << filename);
-    // Testing emitter: V3EmitV::verilogForTree(v3Global.rootp(), *logp);
 
     std::deque report;
 
+    // Rebuild the trigger to original AstSenTree map using equality key comparison, as
+    // merging domains have created new AstSenTree instances which are not in the map
+    std::unordered_map, const AstSenTree*> trigToSen;
+    for (const auto& pair : m_trigToSen) trigToSen.emplace(*pair.first, pair.second);
+
     for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
         if (OrderVarVertex* const vvertexp = dynamic_cast(itp)) {
-            string name(vvertexp->varScp()->prettyName());
+            string name(vvertexp->vscp()->prettyName());
             if (dynamic_cast(itp)) {
                 name += " {PRE}";
             } else if (dynamic_cast(itp)) {
@@ -1636,9 +1028,22 @@ void OrderProcess::processEdgeReport() {
             }
             std::ostringstream os;
             os.setf(std::ios::left);
-            os << "  " << cvtToHex(vvertexp->varScp()) << " " << std::setw(50) << name << " ";
-            AstSenTree* const sentreep = vvertexp->domainp();
-            if (sentreep) V3EmitV::verilogForTree(sentreep, os);
+            os << "  " << cvtToHex(vvertexp->vscp()) << " " << std::setw(50) << name << " ";
+            AstSenTree* const senTreep = vvertexp->domainp();
+            if (senTreep == m_deleteDomainp) {
+                os << "DELETED";
+            } else {
+                for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
+                     senItemp = VN_AS(senItemp->nextp(), SenItem)) {
+                    if (senItemp != senTreep->sensesp()) os << " or ";
+                    const auto it = trigToSen.find(*senItemp);
+                    if (it != trigToSen.end()) {
+                        V3EmitV::verilogForTree(it->second, os);
+                    } else {
+                        V3EmitV::verilogForTree(senItemp, os);
+                    }
+                }
+            }
             report.push_back(os.str());
         }
     }
@@ -1663,7 +1068,7 @@ void OrderProcess::processMoveBuildGraph() {
     m_pomGraph.userClearVertices();
 
     OrderMoveVertexMaker createOrderMoveVertex(&m_pomGraph, &m_pomWaiting);
-    ProcessMoveBuildGraph serialPMBG(&m_graph, &m_pomGraph,
+    ProcessMoveBuildGraph serialPMBG(&m_graph, &m_pomGraph, m_trigToSen,
                                                       &createOrderMoveVertex);
     serialPMBG.build();
 }
@@ -1784,7 +1189,7 @@ void OrderProcess::processMoveOne(OrderMoveVertex* vertexp, OrderMoveDomScope* d
                              << " s=" << cvtToHex(scopep) << " " << lvertexp << endl);
     AstActive* const newActivep
         = processMoveOneLogic(lvertexp, m_pomNewFuncp /*ref*/, m_pomNewStmts /*ref*/);
-    if (newActivep) m_scopetop.addBlocksp(newActivep);
+    if (newActivep) m_result.push_back(newActivep);
     processMoveDoneOne(vertexp);
 }
 
@@ -1796,103 +1201,83 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp,
     AstNode* nodep = lvertexp->nodep();
     AstNodeModule* const modp = scopep->modp();
     UASSERT(modp, "nullptr");
-    if (VN_IS(nodep, SenTree)) {
-        // Just ignore sensitivities, we'll deal with them when we move statements that need them
-    } else {  // Normal logic
-        // Move the logic into a CFunc
-        nodep->unlinkFrBack();
 
-        // Process procedures per statement (unless profCFuncs), so we can split CFuncs within
-        // procedures. Everything else is handled in one go
-        AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure);
-        if (procp && !v3Global.opt.profCFuncs()) {
-            nodep = procp->stmtsp();
-            pushDeletep(procp);
+    // We are move the logic into a CFunc, so unlink it from the AstActive
+    nodep->unlinkFrBack();
+
+    // Process procedures per statement (unless profCFuncs), so we can split CFuncs within
+    // procedures. Everything else is handled in one go
+    bool suspendable = false;
+    bool slow = m_slow;
+    if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) {
+        suspendable = procp->isSuspendable();
+        if (suspendable) slow = slow && !VN_IS(procp, Always);
+        nodep = procp->stmtsp();
+        pushDeletep(procp);
+    }
+
+    // Put suspendable processes into individual functions on their own
+    if (suspendable) newFuncpr = nullptr;
+
+    // When profCFuncs, create a new function for all logic block
+    if (v3Global.opt.profCFuncs()) newFuncpr = nullptr;
+
+    while (nodep) {
+        // Split the CFunc if too large (but not when profCFuncs)
+        if (!suspendable && !v3Global.opt.profCFuncs()
+            && (v3Global.opt.outputSplitCFuncs()
+                && v3Global.opt.outputSplitCFuncs() < newStmtsr)) {
+            // Put every statement into a unique function to ease profiling or reduce function
+            // size
+            newFuncpr = nullptr;
         }
-
-        while (nodep) {
-            // Make or borrow a CFunc to contain the new statements
-            if (v3Global.opt.profCFuncs()
-                || (v3Global.opt.outputSplitCFuncs()
-                    && v3Global.opt.outputSplitCFuncs() < newStmtsr)) {
-                // Put every statement into a unique function to ease profiling or reduce function
-                // size
-                newFuncpr = nullptr;
-            }
-            if (!newFuncpr && domainp != m_deleteDomainp) {
-                const string name = cfuncName(modp, domainp, scopep, nodep);
-                newFuncpr = new AstCFunc(nodep->fileline(), name, scopep);
-                newFuncpr->isStatic(false);
-                newFuncpr->isLoose(true);
-                newStmtsr = 0;
-                if (domainp->hasInitial() || domainp->hasSettle()) newFuncpr->slow(true);
-                scopep->addBlocksp(newFuncpr);
-                // Create top call to it
-                AstCCall* const callp = new AstCCall(nodep->fileline(), newFuncpr);
-                // Where will we be adding the call?
-                AstActive* const newActivep = new AstActive(nodep->fileline(), name, domainp);
-                newActivep->addStmtsp(callp);
-                if (!activep) {
-                    activep = newActivep;
-                } else {
-                    activep->addNext(newActivep);
-                }
-                UINFO(6, "      New " << newFuncpr << endl);
-            }
-
-            AstNode* const nextp = nodep->nextp();
-            // When processing statements in a procedure, unlink the current statement
-            if (nodep->backp()) nodep->unlinkFrBack();
-
-            if (domainp == m_deleteDomainp) {
-                UINFO(4, " Ordering deleting pre-settled " << nodep << endl);
-                VL_DO_DANGLING(pushDeletep(nodep), nodep);
+        if (!newFuncpr && domainp != m_deleteDomainp) {
+            const string name = cfuncName(modp, domainp, scopep, nodep);
+            newFuncpr
+                = new AstCFunc{nodep->fileline(), name, scopep, suspendable ? "VlCoroutine" : ""};
+            newFuncpr->isStatic(false);
+            newFuncpr->isLoose(true);
+            newFuncpr->slow(slow);
+            newStmtsr = 0;
+            scopep->addBlocksp(newFuncpr);
+            // Create top call to it
+            AstCCall* const callp = new AstCCall{nodep->fileline(), newFuncpr};
+            // Where will we be adding the call?
+            AstActive* const newActivep = new AstActive{nodep->fileline(), name, domainp};
+            newActivep->addStmtsp(callp);
+            if (!activep) {
+                activep = newActivep;
             } else {
-                newFuncpr->addStmtsp(nodep);
-                if (v3Global.opt.outputSplitCFuncs()) {
-                    // Add in the number of nodes we're adding
-                    newStmtsr += nodep->nodeCount();
-                }
+                activep->addNext(newActivep);
             }
-
-            nodep = nextp;
+            UINFO(6, "      New " << newFuncpr << endl);
         }
+
+        AstNode* const nextp = nodep->nextp();
+        // When processing statements in a procedure, unlink the current statement
+        if (nodep->backp()) nodep->unlinkFrBack();
+
+        if (domainp == m_deleteDomainp) {
+            VL_DO_DANGLING(pushDeletep(nodep), nodep);
+        } else {
+            newFuncpr->addStmtsp(nodep);
+            // Add in the number of nodes we're adding
+            if (v3Global.opt.outputSplitCFuncs()) newStmtsr += nodep->nodeCount();
+        }
+
+        nodep = nextp;
     }
+    // Put suspendable processes into individual functions on their own
+    if (suspendable) newFuncpr = nullptr;
+
     return activep;
 }
 
-void OrderProcess::processMTasksInitial(InitialLogicE logic_type) {
-    // Emit initial/settle logic. Initial blocks won't be part of the
-    // mtask partition, aren't eligible for parallelism.
-    //
-    int initStmts = 0;
-    AstCFunc* initCFunc = nullptr;
-    const AstScope* lastScopep = nullptr;
-    for (V3GraphVertex* initVxp = m_graph.verticesBeginp(); initVxp;
-         initVxp = initVxp->verticesNextp()) {
-        OrderLogicVertex* const initp = dynamic_cast(initVxp);
-        if (!initp) continue;
-        if ((logic_type == LOGIC_INITIAL) && !initp->domainp()->hasInitial()) continue;
-        if ((logic_type == LOGIC_SETTLE) && !initp->domainp()->hasSettle()) continue;
-        if (initp->scopep() != lastScopep) {
-            // Start new cfunc, don't let the cfunc cross scopes
-            initCFunc = nullptr;
-            lastScopep = initp->scopep();
-        }
-        AstActive* const newActivep
-            = processMoveOneLogic(initp, initCFunc /*ref*/, initStmts /*ref*/);
-        if (newActivep) m_scopetop.addBlocksp(newActivep);
-    }
-}
-
 void OrderProcess::processMTasks() {
     // For nondeterminism debug:
     V3Partition::hashGraphDebug(&m_graph, "V3Order's m_graph");
 
-    processMTasksInitial(LOGIC_INITIAL);
-    processMTasksInitial(LOGIC_SETTLE);
-
-    // We already produced a graph of every var, input, logic, and settle
+    // We already produced a graph of every var, input, and logic
     // block and all dependencies; this is 'm_graph'.
     //
     // Now, starting from m_graph, make a slightly-coarsened graph representing
@@ -1900,7 +1285,8 @@ void OrderProcess::processMTasks() {
     // This is quite similar to the 'm_pomGraph' of the serial code gen:
     V3Graph logicGraph;
     OrderMTaskMoveVertexMaker create_mtask_vertex(&logicGraph);
-    ProcessMoveBuildGraph mtask_pmbg(&m_graph, &logicGraph, &create_mtask_vertex);
+    ProcessMoveBuildGraph mtask_pmbg(&m_graph, &logicGraph, m_trigToSen,
+                                                      &create_mtask_vertex);
     mtask_pmbg.build();
 
     // Needed? We do this for m_pomGraph in serial mode, so do it here too:
@@ -1909,7 +1295,7 @@ void OrderProcess::processMTasks() {
     // Partition logicGraph into LogicMTask's. The partitioner will annotate
     // each vertex in logicGraph with a 'color' which is really an mtask ID
     // in this context.
-    V3Partition partitioner(&logicGraph);
+    V3Partition partitioner(&m_graph, &logicGraph);
     V3Graph mtasks;
     partitioner.go(&mtasks);
 
@@ -1924,44 +1310,45 @@ void OrderProcess::processMTasks() {
     const V3GraphVertex* moveVxp;
     while ((moveVxp = emit_logic.nextp())) {
         const MTaskMoveVertex* const movep = static_cast(moveVxp);
+        // Only care about logic vertices
+        if (!movep->logicp()) continue;
+
         const unsigned mtaskId = movep->color();
         UASSERT(mtaskId > 0, "Every MTaskMoveVertex should have an mtask assignment >0");
-        if (movep->logicp()) {
-            // Add this logic to the per-mtask order
-            mtaskStates[mtaskId].m_logics.push_back(movep->logicp());
 
-            // Since we happen to be iterating over every logic node,
-            // take this opportunity to annotate each AstVar with the id's
-            // of mtasks that consume it and produce it. We'll use this
-            // information in V3EmitC when we lay out var's in memory.
-            const OrderLogicVertex* const logicp = movep->logicp();
-            for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) {
-                const OrderVarVertex* const pre_varp
-                    = dynamic_cast(edgep->fromp());
-                if (!pre_varp) continue;
-                AstVar* const varp = pre_varp->varScp()->varp();
-                // varp depends on logicp, so logicp produces varp,
-                // and vice-versa below
-                varp->addProducingMTaskId(mtaskId);
-            }
-            for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep;
-                 edgep = edgep->outNextp()) {
-                const OrderVarVertex* const post_varp
-                    = dynamic_cast(edgep->top());
-                if (!post_varp) continue;
-                AstVar* const varp = post_varp->varScp()->varp();
-                varp->addConsumingMTaskId(mtaskId);
-            }
-            // TODO? We ignore IO vars here, so those will have empty mtask
-            // signatures. But we could also give those mtask signatures.
+        // Add this logic to the per-mtask order
+        mtaskStates[mtaskId].m_logics.push_back(movep->logicp());
+
+        // Since we happen to be iterating over every logic node,
+        // take this opportunity to annotate each AstVar with the id's
+        // of mtasks that consume it and produce it. We'll use this
+        // information in V3EmitC when we lay out var's in memory.
+        const OrderLogicVertex* const logicp = movep->logicp();
+        for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+            const OrderVarVertex* const pre_varp
+                = dynamic_cast(edgep->fromp());
+            if (!pre_varp) continue;
+            AstVar* const varp = pre_varp->vscp()->varp();
+            // varp depends on logicp, so logicp produces varp,
+            // and vice-versa below
+            varp->addProducingMTaskId(mtaskId);
         }
+        for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+            const OrderVarVertex* const post_varp
+                = dynamic_cast(edgep->top());
+            if (!post_varp) continue;
+            AstVar* const varp = post_varp->vscp()->varp();
+            varp->addConsumingMTaskId(mtaskId);
+        }
+        // TODO? We ignore IO vars here, so those will have empty mtask
+        // signatures. But we could also give those mtask signatures.
     }
 
     // Create the AstExecGraph node which represents the execution
     // of the MTask graph.
     FileLine* const rootFlp = v3Global.rootp()->fileline();
-    AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, "eval"};
-    m_scopetop.addBlocksp(execGraphp);
+    AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, m_tag};
+    m_result.push_back(execGraphp);
 
     // Create CFuncs and bodies for each MTask.
     GraphStream emit_mtasks(&mtasks);
@@ -2020,9 +1407,9 @@ void OrderProcess::processMTasks() {
 //######################################################################
 // OrderVisitor - Top processing
 
-void OrderProcess::process() {
+void OrderProcess::process(bool multiThreaded) {
     // Dump data
-    if (dumpGraph()) m_graph.dumpDotFilePrefixed("orderg_pre");
+    if (dumpGraph()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_pre");
 
     // Break cycles. Each strongly connected subgraph (including cutable
     // edges) will have its own color, and corresponds to a loop in the
@@ -2030,36 +1417,27 @@ void OrderProcess::process() {
     // edges are actually still there, just with weight 0).
     UINFO(2, "  Acyclic & Order...\n");
     m_graph.acyclic(&V3GraphEdge::followAlwaysTrue);
-    if (dumpGraph()) m_graph.dumpDotFilePrefixed("orderg_acyc");
+    if (dumpGraph()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_acyc");
 
     // Assign ranks so we know what to follow
     // Then, sort vertices and edges by that ordering
     m_graph.order();
-    if (dumpGraph()) m_graph.dumpDotFilePrefixed("orderg_order");
-
-    // This finds everything that can be traced from an input (which by
-    // definition are the source clocks). After this any vertex which was
-    // traced has isFromInput() true.
-    UINFO(2, "  Process Clocks...\n");
-    processInputs();  // must be before processCircular
-
-    UINFO(2, "  Process Circulars...\n");
-    processCircular();  // must be before processDomains
+    if (dumpGraph()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_order");
 
     // Assign logic vertices to new domains
     UINFO(2, "  Domains...\n");
     processDomains();
-    if (dumpGraph()) m_graph.dumpDotFilePrefixed("orderg_domain");
+    if (dumpGraph()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_domain");
 
     if (dump()) processEdgeReport();
 
-    if (!v3Global.opt.mtasks()) {
+    if (!multiThreaded) {
         UINFO(2, "  Construct Move Graph...\n");
         processMoveBuildGraph();
         // Different prefix (ordermv) as it's not the same graph
-        if (dumpGraph() >= 4) m_pomGraph.dumpDotFilePrefixed("ordermv_start");
+        if (dumpGraph() >= 4) m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_start");
         m_pomGraph.removeRedundantEdges(&V3GraphEdge::followAlwaysTrue);
-        if (dumpGraph() >= 4) m_pomGraph.dumpDotFilePrefixed("ordermv_simpl");
+        if (dumpGraph() >= 4) m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_simpl");
 
         UINFO(2, "  Move...\n");
         processMove();
@@ -2068,25 +1446,46 @@ void OrderProcess::process() {
         processMTasks();
     }
 
-    // Any SC inputs feeding a combo domain must be marked, so we can make them sc_sensitive
-    UINFO(2, "  Sensitive...\n");
-    processSensitive();  // must be after processDomains
-
     // Dump data
-    if (dumpGraph()) m_graph.dumpDotFilePrefixed("orderg_done");
+    if (dumpGraph()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_done");
 }
 
 //######################################################################
-// Order class functions
 
-void V3Order::orderAll(AstNetlist* netlistp) {
-    UINFO(2, __FUNCTION__ << ": " << endl);
-    // Propagate 'clocker' attribute through logic
-    OrderClkMarkVisitor::process(netlistp);
-    // Build ordering graph
-    std::unique_ptr orderGraph = OrderBuildVisitor::process(netlistp);
-    // Order the netlist
-    OrderProcess::main(netlistp, *orderGraph);
-    // Dump tree
-    V3Global::dumpCheckGlobalTree("order", 0, dumpTree() >= 3);
+namespace V3Order {
+
+AstCFunc* order(AstNetlist* netlistp,  //
+                const std::vector& logic,  //
+                const std::unordered_map& trigToSen,
+                const string& tag,  //
+                bool parallel,  //
+                bool slow,  //
+                const ExternalDomainsProvider& externalDomains) {
+    // Order the code
+    const std::unique_ptr graph
+        = OrderBuildVisitor::process(netlistp, logic, trigToSen);
+    const auto& nodeps
+        = OrderProcess::main(netlistp, *graph, trigToSen, tag, parallel, slow, externalDomains);
+
+    // Create the result function
+    AstScope* const scopeTopp = netlistp->topScopep()->scopep();
+    AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), "_eval_" + tag, scopeTopp, ""};
+    funcp->dontCombine(true);
+    funcp->isStatic(false);
+    funcp->isLoose(true);
+    funcp->slow(slow);
+    funcp->isConst(false);
+    funcp->declPrivate(true);
+    scopeTopp->addBlocksp(funcp);
+
+    // Add ordered statements to the result function
+    for (AstNode* const nodep : nodeps) funcp->addStmtsp(nodep);
+
+    // Dispose of the remnants of the inputs
+    for (auto* const lbsp : logic) lbsp->deleteActives();
+
+    // Done
+    return funcp;
 }
+
+}  // namespace V3Order
diff --git a/src/V3Order.h b/src/V3Order.h
index fa9724473..6beac4031 100644
--- a/src/V3Order.h
+++ b/src/V3Order.h
@@ -20,13 +20,36 @@
 #include "config_build.h"
 #include "verilatedos.h"
 
+#include 
+#include 
+#include 
+
+class AstCFunc;
 class AstNetlist;
+class AstSenItem;
+class AstSenTree;
+class AstVarScope;
+
+namespace V3Sched {
+struct LogicByScope;
+};  // namespace V3Sched
 
 //============================================================================
 
-class V3Order final {
-public:
-    static void orderAll(AstNetlist* netlistp);
-};
+namespace V3Order {
+
+using ExternalDomainsProvider = std::function&)>;
+
+AstCFunc* order(
+    AstNetlist* netlistp,  //
+    const std::vector& logic,  //
+    const std::unordered_map& trigToSen,
+    const string& tag,  //
+    bool parallel,  //
+    bool slow,  //
+    const ExternalDomainsProvider& externalDomains
+    = [](const AstVarScope*, std::vector&) {});
+
+};  // namespace V3Order
 
 #endif  // Guard
diff --git a/src/V3OrderGraph.h b/src/V3OrderGraph.h
index 109dfb3cb..a8093944d 100644
--- a/src/V3OrderGraph.h
+++ b/src/V3OrderGraph.h
@@ -13,25 +13,52 @@
 // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
 //
 //*************************************************************************
-//  OrderGraph Class Hierarchy:
 //
-//      V3GraphVertex
-//        OrderMoveVertex
-//        MTaskMoveVertex
-//        OrderEitherVertex
-//          OrderInputsVertex
-//          OrderLogicVertex
-//          OrderVarVertex
-//            OrderVarStdVertex
-//            OrderVarPreVertex
-//            OrderVarPostVertex
-//            OrderVarPordVertex
+// OrderGraph is a bipartite graph, with the two parts being formed of only OrderLogicVertex and
+// OrderVarVertex vertices respectively (i.e.: edges are always between OrderLogicVertex and
+// OrderVarVertex, and never between two OrderLogicVertex or OrderVarVertex). The graph represents
+// both fine-grained dependencies, and additional ordering constraints between logic blocks and
+// variables. The fact that OrderGraph is bipartite is important and we take advantage of this fact
+// in various algorithms, so this property must be maintained.
+//
+// Both OrderLogicVertex and OrderVarVertex derives from OrderEitherVertex, so OrderGraph is
+// composed only of OrderEitherVertex vertices.
+//
+// OrderLogicVertex holds a 'logic block', which is just some computational construct that is
+// ordered as a single unit. Ordering of these logic blocks is determined by the variables they
+// read and write, which is represented by the edges between OrderLogicVertex and OrderVarVertex
+// instances (and hence the graph is bipartite).
+//
+// OrderVarVertex is abstract, and has various concrete subtypes that represent various ordering
+// constraints imposed by variables accessed by logic blocks. The concrete subtypes and their
+// roles are:
+//
+// OrderVarStdVertex:   Data dependencies for combinational logic and delayed assignment
+//                      updates (AssignPost).
+// OrderVarPostVertex:  Ensures all sequential logic blocks reading a signal do so before any
+//                      combinational or delayed assignments update that signal.
+// OrderVarPordVertex:  Ensures a _d = _q AssignPre used to implement delayed (non-blocking)
+//                      assignments is the first write of a _d, before any sequential blocks
+//                      write to that _d.
+// OrderVarPreVertex:   This is an optimization. Try to ensure that a _d = _q AssignPre is the
+//                      last read of a _q, after all reads of that _q by sequential logic. The
+//                      model is still correct if we cannot satisfy this due to other interfering
+//                      constraints. If respecting this constraint is possible, then combined
+//                      with the OrderVarPordVertex constraint we get that all writes to _d are
+//                      after all reads of a _q, which then allows us to eliminate the _d
+//                      completely and assign to the _q directly. This means these delayed
+//                      assignments can be implemented without temporary storage (the redundant
+//                      storage is eliminated in V3LifePost).
+//
+// Ordering constraints are represented by directed edges, where the source of an edge needs to be
+// ordered before the sink of an edge. A constraint can be either hard (must be satisfied),
+// represented by a non cutable edge, or a constraint can be soft (ideally should be satisfied, but
+// is ok not to if other hard constraints interfere), represented by a cutable edge. Edges
+// otherwise carry no additional information. TODO: what about weight?
+//
+// Note: It is required for hard (non-cutable) constraints to form a DAG, but together with the
+// soft constraints the graph can be arbitrary so long as it remains bipartite.
 //
-//      V3GraphEdge
-//        OrderEdge
-//          OrderComboCutEdge
-//          OrderPostCutEdge
-//          OrderPreCutEdge
 //*************************************************************************
 
 #ifndef VERILATOR_V3ORDERGRAPH_H_
@@ -43,468 +70,218 @@
 #include "V3Ast.h"
 #include "V3Graph.h"
 
-#include 
+class OrderLogicVertex;
+class OrderVarVertex;
 
-class OrderVisitor;
-class OrderMoveVertex;
-class OrderMoveVertexMaker;
-class OrderMoveDomScope;
-
-//######################################################################
+//======================================================================
 
 enum OrderWeights : uint8_t {
-    WEIGHT_INPUT = 1,  // Low weight just so dot graph looks nice
     WEIGHT_COMBO = 1,  // Breakable combo logic
     WEIGHT_POST = 2,  // Post-delayed used var
     WEIGHT_PRE = 3,  // Breakable pre-delayed used var
     WEIGHT_MEDIUM = 8,  // Medium weight just so dot graph looks nice
-    WEIGHT_NORMAL = 32
-};  // High weight just so dot graph looks nice
-
-struct OrderVEdgeType {
-    enum en : uint8_t {
-        VERTEX_UNKNOWN = 0,
-        VERTEX_INPUTS,
-        VERTEX_LOGIC,
-        VERTEX_VARSTD,
-        VERTEX_VARPRE,
-        VERTEX_VARPOST,
-        VERTEX_VARPORD,
-        VERTEX_VARSETTLE,
-        VERTEX_MOVE,
-        EDGE_STD,
-        EDGE_COMBOCUT,
-        EDGE_PRECUT,
-        EDGE_POSTCUT,
-        _ENUM_END
-    };
-    const char* ascii() const {
-        static const char* const names[]
-            = {"%E-vedge",      "VERTEX_INPUTS",  "VERTEX_LOGIC",   "VERTEX_VARSTD",
-               "VERTEX_VARPRE", "VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_VARSETTLE",
-               "VERTEX_MOVE",   "EDGE_STD",       "EDGE_COMBOCUT",  "EDGE_PRECUT",
-               "EDGE_POSTCUT",  "_ENUM_END"};
-        return names[m_e];
-    }
-    enum en m_e;
-    OrderVEdgeType()
-        : m_e{VERTEX_UNKNOWN} {}
-    // cppcheck-suppress noExplicitConstructor
-    constexpr OrderVEdgeType(en _e)
-        : m_e{_e} {}
-    explicit OrderVEdgeType(int _e)
-        : m_e(static_cast(_e)) {}  // Need () or GCC 4.8 false warning
-    constexpr operator en() const { return m_e; }
+    WEIGHT_NORMAL = 32  // High weight just so dot graph looks nice
 };
-constexpr bool operator==(const OrderVEdgeType& lhs, const OrderVEdgeType& rhs) {
-    return lhs.m_e == rhs.m_e;
-}
-constexpr bool operator==(const OrderVEdgeType& lhs, OrderVEdgeType::en rhs) {
-    return lhs.m_e == rhs;
-}
-constexpr bool operator==(OrderVEdgeType::en lhs, const OrderVEdgeType& rhs) {
-    return lhs == rhs.m_e;
-}
 
-//######################################################################
-// Graph types
+//======================================================================
+// Graph type
 
 class OrderGraph final : public V3Graph {
 public:
-    OrderGraph() = default;
-    ~OrderGraph() override = default;
-    // Methods
-    void loopsVertexCb(V3GraphVertex* vertexp) override;
+    // METHODS
+
+    // Methods to add edges representing constraints, utilizing the type system to help us ensure
+    // the graph remains bipartite.
+    inline void addHardEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight);
+    inline void addHardEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight);
+    inline void addSoftEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight);
+    inline void addSoftEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight);
 };
 
-//######################################################################
+//======================================================================
 // Vertex types
 
 class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex {
-    AstScope* const m_scopep;  // Scope the vertex is in
-    AstSenTree* m_domainp;  // Clock domain (nullptr = to be computed as we iterate)
-    bool m_isFromInput = false;  // From input, or derived therefrom (conservatively false)
-protected:
-    OrderEitherVertex(V3Graph* graphp, const OrderEitherVertex& old)
-        : V3GraphVertex{graphp, old}
-        , m_scopep{old.m_scopep}
-        , m_domainp{old.m_domainp}
-        , m_isFromInput{old.m_isFromInput} {}
+    // Event domain of vertex. For OrderLogicVertex this represents the conditions when the logic
+    // block must be executed. For OrderVarVertex, this is the union of the domains of all the
+    // OrderLogicVertex vertices that drive the variable. If initially set to nullptr (e.g.: all
+    // OrderVarVertex and those OrderLogicVertices that represent combinational logic), then the
+    // ordering algorithm will compute the domain automatically based on the edges representing
+    // data-flow (those between OrderLogicVertex and OrderVarStdVertex), otherwise the domain is
+    // as given (e.g.: for those OrderLogicVertices that represent clocked logic).
+    AstSenTree* m_domainp;
 
-public:
-    OrderEitherVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp)
+protected:
+    // CONSTRUCTOR
+    OrderEitherVertex(OrderGraph* graphp, AstSenTree* domainp)
         : V3GraphVertex{graphp}
-        , m_scopep{scopep}
         , m_domainp{domainp} {}
     ~OrderEitherVertex() override = default;
-    OrderEitherVertex* clone(V3Graph* graphp) const override = 0;
-    // Methods
-    virtual OrderVEdgeType type() const = 0;
-    virtual bool domainMatters() = 0;  // Must be in same domain when cross edge to this vertex
-    string dotName() const override { return cvtToHex(m_scopep) + "_"; }
-    // ACCESSORS
-    void domainp(AstSenTree* domainp) { m_domainp = domainp; }
-    AstScope* scopep() const { return m_scopep; }
-    AstSenTree* domainp() const { return m_domainp; }
-    void isFromInput(bool flag) { m_isFromInput = flag; }
-    bool isFromInput() const { return m_isFromInput; }
-};
-
-class OrderInputsVertex final : public OrderEitherVertex {
-    OrderInputsVertex(V3Graph* graphp, const OrderInputsVertex& old)
-        : OrderEitherVertex{graphp, old} {}
 
 public:
-    OrderInputsVertex(V3Graph* graphp, AstSenTree* domainp)
-        : OrderEitherVertex{graphp, nullptr, domainp} {
-        isFromInput(true);  // By definition
+    // METHODS
+    virtual bool domainMatters() = 0;
+
+    // ACCESSORS
+    AstSenTree* domainp() const { return m_domainp; }
+    void domainp(AstSenTree* domainp) {
+#if VL_DEBUG
+        UASSERT(!m_domainp, "Domain should only be set once");
+#endif
+        m_domainp = domainp;
     }
-    ~OrderInputsVertex() override = default;
-    OrderInputsVertex* clone(V3Graph* graphp) const override {
-        return new OrderInputsVertex(graphp, *this);
-    }
-    OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_INPUTS; }
-    string name() const override { return "*INPUTS*"; }
-    string dotColor() const override { return "green"; }
-    string dotName() const override { return ""; }
-    string dotShape() const override { return "invhouse"; }
-    bool domainMatters() override { return false; }
 };
 
 class OrderLogicVertex final : public OrderEitherVertex {
-    AstNode* const m_nodep;
-
-protected:
-    OrderLogicVertex(V3Graph* graphp, const OrderLogicVertex& old)
-        : OrderEitherVertex{graphp, old}
-        , m_nodep{old.m_nodep} {}
+    AstNode* const m_nodep;  // The logic this vertex represents
+    AstScope* const m_scopep;  // Scope the logic is under
+    AstSenTree* const m_hybridp;  // Additional sensitivities for hybrid combinational logic
 
 public:
-    OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstNode* nodep)
-        : OrderEitherVertex{graphp, scopep, domainp}
-        , m_nodep{nodep} {}
-    ~OrderLogicVertex() override = default;
-    OrderLogicVertex* clone(V3Graph* graphp) const override {
-        return new OrderLogicVertex(graphp, *this);
+    // CONSTRUCTOR
+    OrderLogicVertex(OrderGraph* graphp, AstScope* scopep, AstSenTree* domainp,
+                     AstSenTree* hybridp, AstNode* nodep)
+        : OrderEitherVertex{graphp, domainp}
+        , m_nodep{nodep}
+        , m_scopep{scopep}
+        , m_hybridp{hybridp} {
+        UASSERT_OBJ(scopep, nodep, "Must not be null");
+        UASSERT_OBJ(!(domainp && hybridp), nodep, "Cannot have bot domainp and hybridp set");
     }
-    OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_LOGIC; }
+    ~OrderLogicVertex() override = default;
+
+    // METHODS
     bool domainMatters() override { return true; }
+
     // ACCESSORS
+    AstNode* nodep() const { return m_nodep; }
+    AstScope* scopep() const { return m_scopep; }
+    AstSenTree* hybridp() const { return m_hybridp; }
+
+    // LCOV_EXCL_START // Debug code
     string name() const override {
         return (cvtToHex(m_nodep) + "\\n " + cvtToStr(nodep()->typeName()));
     }
-    AstNode* nodep() const { return m_nodep; }
     string dotShape() const override { return VN_IS(m_nodep, Active) ? "doubleoctagon" : "rect"; }
+    // LCOV_EXCL_STOP
 };
 
 class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex {
-    AstVarScope* const m_varScp;
-    bool m_isClock = false;  // Used as clock
-    bool m_isDelayed = false;  // Set in a delayed assignment
-protected:
-    OrderVarVertex(V3Graph* graphp, const OrderVarVertex& old)
-        : OrderEitherVertex{graphp, old}
-        , m_varScp{old.m_varScp}
-        , m_isClock{old.m_isClock}
-        , m_isDelayed{old.m_isDelayed} {}
+    AstVarScope* const m_vscp;
 
 public:
-    OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
-        : OrderEitherVertex{graphp, scopep, nullptr}
-        , m_varScp{varScp} {}
+    // CONSTRUCTOR
+    OrderVarVertex(OrderGraph* graphp, AstVarScope* vscp)
+        : OrderEitherVertex{graphp, nullptr}
+        , m_vscp{vscp} {}
     ~OrderVarVertex() override = default;
-    OrderVarVertex* clone(V3Graph* graphp) const override = 0;
-    OrderVEdgeType type() const override = 0;
-    FileLine* fileline() const override { return varScp()->fileline(); }
+
     // ACCESSORS
-    AstVarScope* varScp() const { return m_varScp; }
-    void isClock(bool flag) { m_isClock = flag; }
-    bool isClock() const { return m_isClock; }
-    void isDelayed(bool flag) { m_isDelayed = flag; }
-    bool isDelayed() const { return m_isDelayed; }
-    string dotShape() const override { return "ellipse"; }
+    AstVarScope* vscp() const { return m_vscp; }
+
+    // LCOV_EXCL_START // Debug code
+    string dotShape() const override final { return "ellipse"; }
+    virtual string nameSuffix() const = 0;
+    string name() const override final {
+        return cvtToHex(m_vscp) + " " + nameSuffix() + "\\n " + m_vscp->name();
+    }
+    // LCOV_EXCL_STOP
 };
 
 class OrderVarStdVertex final : public OrderVarVertex {
-    OrderVarStdVertex(V3Graph* graphp, const OrderVarStdVertex& old)
-        : OrderVarVertex{graphp, old} {}
-
 public:
-    OrderVarStdVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
-        : OrderVarVertex{graphp, scopep, varScp} {}
+    // CONSTRUCTOR
+    OrderVarStdVertex(OrderGraph* graphp, AstVarScope* vscp)
+        : OrderVarVertex{graphp, vscp} {}
     ~OrderVarStdVertex() override = default;
-    OrderVarStdVertex* clone(V3Graph* graphp) const override {
-        return new OrderVarStdVertex(graphp, *this);
-    }
-    OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARSTD; }
-    string name() const override { return (cvtToHex(varScp()) + "\\n " + varScp()->name()); }
-    string dotColor() const override { return "grey"; }
-    bool domainMatters() override { return true; }
-};
-class OrderVarPreVertex final : public OrderVarVertex {
-    OrderVarPreVertex(V3Graph* graphp, const OrderVarPreVertex& old)
-        : OrderVarVertex{graphp, old} {}
 
-public:
-    OrderVarPreVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
-        : OrderVarVertex{graphp, scopep, varScp} {}
-    ~OrderVarPreVertex() override = default;
-    OrderVarPreVertex* clone(V3Graph* graphp) const override {
-        return new OrderVarPreVertex(graphp, *this);
-    }
-    OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARPRE; }
-    string name() const override { return (cvtToHex(varScp()) + " PRE\\n " + varScp()->name()); }
-    string dotColor() const override { return "green"; }
-    bool domainMatters() override { return false; }
-};
-class OrderVarPostVertex final : public OrderVarVertex {
-    OrderVarPostVertex(V3Graph* graphp, const OrderVarPostVertex& old)
-        : OrderVarVertex{graphp, old} {}
-
-public:
-    OrderVarPostVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
-        : OrderVarVertex{graphp, scopep, varScp} {}
-    OrderVarPostVertex* clone(V3Graph* graphp) const override {
-        return new OrderVarPostVertex(graphp, *this);
-    }
-    OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARPOST; }
-    ~OrderVarPostVertex() override = default;
-    string name() const override { return (cvtToHex(varScp()) + " POST\\n " + varScp()->name()); }
-    string dotColor() const override { return "red"; }
-    bool domainMatters() override { return false; }
-};
-class OrderVarPordVertex final : public OrderVarVertex {
-    OrderVarPordVertex(V3Graph* graphp, const OrderVarPordVertex& old)
-        : OrderVarVertex{graphp, old} {}
-
-public:
-    OrderVarPordVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
-        : OrderVarVertex{graphp, scopep, varScp} {}
-    ~OrderVarPordVertex() override = default;
-    OrderVarPordVertex* clone(V3Graph* graphp) const override {
-        return new OrderVarPordVertex(graphp, *this);
-    }
-    OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARPORD; }
-    string name() const override { return (cvtToHex(varScp()) + " PORD\\n " + varScp()->name()); }
-    string dotColor() const override { return "blue"; }
-    bool domainMatters() override { return false; }
-};
-
-//######################################################################
-//--- Following only under the move graph, not the main graph
-
-class OrderMoveVertex final : public V3GraphVertex {
-    enum OrderMState : uint8_t { POM_WAIT, POM_READY, POM_MOVED };
-
-    OrderLogicVertex* const m_logicp;
-    OrderMState m_state;  // Movement state
-    OrderMoveDomScope* m_domScopep;  // Domain/scope list information
-
-protected:
-    friend class OrderProcess;
-    friend class OrderMoveVertexMaker;
-    // These only contain the "next" item,
-    // for the head of the list, see the same var name under OrderVisitor
-    V3ListEnt m_pomWaitingE;  // List of nodes needing inputs to become ready
-    V3ListEnt m_readyVerticesE;  // List of ready under domain/scope
-public:
-    // CONSTRUCTORS
-    OrderMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp)
-        : V3GraphVertex{graphp}
-        , m_logicp{logicp}
-        , m_state{POM_WAIT}
-        , m_domScopep{nullptr} {}
-    ~OrderMoveVertex() override = default;
-    OrderMoveVertex* clone(V3Graph* graphp) const override {
-        v3fatalSrc("Unsupported");
-        return nullptr;
-    }
     // METHODS
-    virtual OrderVEdgeType type() const { return OrderVEdgeType::VERTEX_MOVE; }
-    string dotColor() const override {
-        if (logicp()) {
-            return logicp()->dotColor();
-        } else {
-            return "";
-        }
-    }
-    FileLine* fileline() const override {
-        if (logicp()) {
-            return logicp()->fileline();
-        } else {
-            return nullptr;
-        }
-    }
-    string name() const override {
-        string nm;
-        if (VL_UNCOVERABLE(!logicp())) {  // Avoid crash when debugging
-            nm = "nul";  // LCOV_EXCL_LINE
-        } else {
-            nm = logicp()->name();
-            nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp())
-                   + " s=" + cvtToHex(logicp()->scopep()));
-        }
-        return nm;
-    }
-    OrderLogicVertex* logicp() const { return m_logicp; }
-    bool isWait() const { return m_state == POM_WAIT; }
-    void setReady() {
-        UASSERT(m_state == POM_WAIT, "Wait->Ready on node not in proper state");
-        m_state = POM_READY;
-    }
-    void setMoved() {
-        UASSERT(m_state == POM_READY, "Ready->Moved on node not in proper state");
-        m_state = POM_MOVED;
-    }
-    OrderMoveDomScope* domScopep() const { return m_domScopep; }
-    OrderMoveVertex* pomWaitingNextp() const { return m_pomWaitingE.nextp(); }
-    void domScopep(OrderMoveDomScope* ds) { m_domScopep = ds; }
+    bool domainMatters() override { return true; }
+
+    // LCOV_EXCL_START // Debug code
+    string nameSuffix() const override { return ""; }
+    string dotColor() const override { return "grey"; }
+    // LCOV_EXCL_STOP
 };
 
-// Similar to OrderMoveVertex, but modified for threaded code generation.
-class MTaskMoveVertex final : public V3GraphVertex {
-    //  This could be more compact, since we know m_varp and m_logicp
-    //  cannot both be set. Each MTaskMoveVertex represents a logic node
-    //  or a var node, it can't be both.
-    OrderLogicVertex* const m_logicp;  // Logic represented by this vertex
-    const OrderEitherVertex* const m_varp;  // Var represented by this vertex
-    const AstScope* const m_scopep;
-    const AstSenTree* const m_domainp;
-
-protected:
-    friend class OrderVisitor;
-
+class OrderVarPreVertex final : public OrderVarVertex {
 public:
-    MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp,
-                    const AstScope* scopep, const AstSenTree* domainp)
-        : V3GraphVertex{graphp}
-        , m_logicp{logicp}
-        , m_varp{varp}
-        , m_scopep{scopep}
-        , m_domainp{domainp} {
-        UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n");
-    }
-    ~MTaskMoveVertex() override = default;
-    MTaskMoveVertex* clone(V3Graph* graphp) const override {
-        v3fatalSrc("Unsupported");
-        return nullptr;
-    }
-    virtual OrderVEdgeType type() const { return OrderVEdgeType::VERTEX_MOVE; }
-    string dotColor() const override {
-        if (logicp()) {
-            return logicp()->dotColor();
-        } else {
-            return "yellow";
-        }
-    }
-    string name() const override {
-        string nm;
-        if (logicp()) {
-            nm = logicp()->name();
-            nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp()) + " s="
-                   + cvtToHex(logicp()->scopep())
-                   // "color()" represents the mtask ID.
-                   + "\\nt=" + cvtToStr(color()));
-        } else {
-            nm = "nolog\\nt=" + cvtToStr(color());
-        }
-        return nm;
-    }
-    // ACCESSORS
-    OrderLogicVertex* logicp() const { return m_logicp; }
-    const OrderEitherVertex* varp() const { return m_varp; }
-    const AstScope* scopep() const { return m_scopep; }
-    const AstSenTree* domainp() const { return m_domainp; }
+    // CONSTRUCTOR
+    OrderVarPreVertex(OrderGraph* graphp, AstVarScope* vscp)
+        : OrderVarVertex{graphp, vscp} {}
+    ~OrderVarPreVertex() override = default;
+
+    // METHODS
+    bool domainMatters() override { return false; }
+
+    // LCOV_EXCL_START // Debug code
+    string nameSuffix() const override { return "PRE"; }
+    string dotColor() const override { return "green"; }
+    // LCOV_EXCL_STOP
 };
 
-//######################################################################
-// Edge types
-
-class OrderEdge VL_NOT_FINAL : public V3GraphEdge {
-protected:
-    OrderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, const OrderEdge& old)
-        : V3GraphEdge{graphp, fromp, top, old} {}
-
+class OrderVarPostVertex final : public OrderVarVertex {
 public:
-    OrderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight,
-              bool cutable = false)
+    // CONSTRUCTOR
+    OrderVarPostVertex(OrderGraph* graphp, AstVarScope* vscp)
+        : OrderVarVertex{graphp, vscp} {}
+    ~OrderVarPostVertex() override = default;
+
+    // METHODS
+    bool domainMatters() override { return false; }
+
+    // LCOV_EXCL_START // Debug code
+    string nameSuffix() const override { return "POST"; }
+    string dotColor() const override { return "red"; }
+    // LCOV_EXCL_STOP
+};
+
+class OrderVarPordVertex final : public OrderVarVertex {
+public:
+    // CONSTRUCTOR
+    OrderVarPordVertex(OrderGraph* graphp, AstVarScope* vscp)
+        : OrderVarVertex{graphp, vscp} {}
+    ~OrderVarPordVertex() override = default;
+
+    // METHODS
+    bool domainMatters() override { return false; }
+
+    // LCOV_EXCL_START // Debug code
+    string nameSuffix() const override { return "PORD"; }
+    string dotColor() const override { return "blue"; }
+    // LCOV_EXCL_STOP
+};
+
+//======================================================================
+// Edge type
+
+class OrderEdge final : public V3GraphEdge {
+    friend class OrderGraph;  // Only the OrderGraph can create these
+    // CONSTRUCTOR
+    OrderEdge(OrderGraph* graphp, OrderEitherVertex* fromp, OrderEitherVertex* top, int weight,
+              bool cutable)
         : V3GraphEdge{graphp, fromp, top, weight, cutable} {}
     ~OrderEdge() override = default;
-    virtual OrderVEdgeType type() const { return OrderVEdgeType::EDGE_STD; }
-    OrderEdge* clone(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) const override {
-        return new OrderEdge(graphp, fromp, top, *this);
-    }
-    // When ordering combo blocks with stronglyConnected, follow edges not
-    // involving pre/pos variables
-    virtual bool followComboConnected() const { return true; }
-    static bool followComboConnected(const V3GraphEdge* edgep) {
-        const OrderEdge* const oedgep = dynamic_cast(edgep);
-        if (!oedgep) v3fatalSrc("Following edge of non-OrderEdge type");
-        return (oedgep->followComboConnected());
-    }
+
+    // LCOV_EXCL_START // Debug code
+    string dotColor() const override { return cutable() ? "green" : "red"; }
+    // LCOV_EXCL_STOP
 };
 
-class OrderComboCutEdge final : public OrderEdge {
-    // Edge created from output of combo logic
-    // Breakable if the output var is also a input,
-    // in which case we'll need a change detect loop around this var.
-    OrderComboCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top,
-                      const OrderComboCutEdge& old)
-        : OrderEdge{graphp, fromp, top, old} {}
+//======================================================================
+// Inline methods
 
-public:
-    OrderComboCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
-        : OrderEdge{graphp, fromp, top, WEIGHT_COMBO, CUTABLE} {}
-    OrderVEdgeType type() const override { return OrderVEdgeType::EDGE_COMBOCUT; }
-    ~OrderComboCutEdge() override = default;
-    OrderComboCutEdge* clone(V3Graph* graphp, V3GraphVertex* fromp,
-                             V3GraphVertex* top) const override {
-        return new OrderComboCutEdge(graphp, fromp, top, *this);
-    }
-    string dotColor() const override { return "yellowGreen"; }
-    bool followComboConnected() const override { return true; }
-};
-
-class OrderPostCutEdge final : public OrderEdge {
-    // Edge created from output of post assignment
-    // Breakable if the output var feeds back to input combo logic or another clock pin
-    // in which case we'll need a change detect loop around this var.
-    OrderPostCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top,
-                     const OrderPostCutEdge& old)
-        : OrderEdge{graphp, fromp, top, old} {}
-
-public:
-    OrderPostCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
-        : OrderEdge{graphp, fromp, top, WEIGHT_COMBO, CUTABLE} {}
-    OrderVEdgeType type() const override { return OrderVEdgeType::EDGE_POSTCUT; }
-    ~OrderPostCutEdge() override = default;
-    OrderPostCutEdge* clone(V3Graph* graphp, V3GraphVertex* fromp,
-                            V3GraphVertex* top) const override {
-        return new OrderPostCutEdge(graphp, fromp, top, *this);
-    }
-    string dotColor() const override { return "PaleGreen"; }
-    bool followComboConnected() const override { return false; }
-};
-
-class OrderPreCutEdge final : public OrderEdge {
-    // Edge created from var_PREVAR->consuming logic vertex
-    // Always breakable, just results in performance loss
-    // in which case we can't optimize away the pre/post delayed assignments
-    OrderPreCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top,
-                    const OrderPreCutEdge& old)
-        : OrderEdge{graphp, fromp, top, old} {}
-
-public:
-    OrderPreCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
-        : OrderEdge{graphp, fromp, top, WEIGHT_PRE, CUTABLE} {}
-    OrderVEdgeType type() const override { return OrderVEdgeType::EDGE_PRECUT; }
-    OrderPreCutEdge* clone(V3Graph* graphp, V3GraphVertex* fromp,
-                           V3GraphVertex* top) const override {
-        return new OrderPreCutEdge(graphp, fromp, top, *this);
-    }
-    ~OrderPreCutEdge() override = default;
-    string dotColor() const override { return "khaki"; }
-    bool followComboConnected() const override { return false; }
-};
+void OrderGraph::addHardEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight) {
+    new OrderEdge{this, fromp, top, weight, /* cutable: */ false};
+}
+void OrderGraph::addHardEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight) {
+    new OrderEdge{this, fromp, top, weight, /* cutable: */ false};
+}
+void OrderGraph::addSoftEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight) {
+    new OrderEdge{this, fromp, top, weight, /* cutable: */ true};
+}
+void OrderGraph::addSoftEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight) {
+    new OrderEdge{this, fromp, top, weight, /* cutable: */ true};
+}
 
 #endif  // Guard
diff --git a/src/V3OrderMoveGraph.h b/src/V3OrderMoveGraph.h
new file mode 100644
index 000000000..bfcdb239a
--- /dev/null
+++ b/src/V3OrderMoveGraph.h
@@ -0,0 +1,141 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Ordering graph
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// TODO: Fix comment
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3ORDERMOVEGRAPH_H_
+#define VERILATOR_V3ORDERMOVEGRAPH_H_
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+#include "V3Graph.h"
+#include "V3OrderGraph.h"
+
+#include 
+
+class OrderMoveDomScope;
+
+class OrderMoveVertex final : public V3GraphVertex {
+    enum OrderMState : uint8_t { POM_WAIT, POM_READY, POM_MOVED };
+
+    OrderLogicVertex* const m_logicp;
+    OrderMState m_state;  // Movement state
+    OrderMoveDomScope* m_domScopep;  // Domain/scope list information
+
+protected:
+    friend class OrderProcess;
+    friend class OrderMoveVertexMaker;
+    // These only contain the "next" item,
+    // for the head of the list, see the same var name under OrderProcess
+    V3ListEnt m_pomWaitingE;  // List of nodes needing inputs to become ready
+    V3ListEnt m_readyVerticesE;  // List of ready under domain/scope
+public:
+    // CONSTRUCTORS
+    OrderMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp)
+        : V3GraphVertex{graphp}
+        , m_logicp{logicp}
+        , m_state{POM_WAIT}
+        , m_domScopep{nullptr} {}
+    ~OrderMoveVertex() override = default;
+
+    // METHODS
+    string dotColor() const override {
+        if (logicp()) {
+            return logicp()->dotColor();
+        } else {
+            return "";
+        }
+    }
+
+    string name() const override {
+        string nm;
+        if (VL_UNCOVERABLE(!logicp())) {  // Avoid crash when debugging
+            nm = "nul";  // LCOV_EXCL_LINE
+        } else {
+            nm = logicp()->name();
+            nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp())
+                   + " s=" + cvtToHex(logicp()->scopep()));
+        }
+        return nm;
+    }
+    OrderLogicVertex* logicp() const { return m_logicp; }
+    bool isWait() const { return m_state == POM_WAIT; }
+    void setReady() {
+        UASSERT(m_state == POM_WAIT, "Wait->Ready on node not in proper state");
+        m_state = POM_READY;
+    }
+    void setMoved() {
+        UASSERT(m_state == POM_READY, "Ready->Moved on node not in proper state");
+        m_state = POM_MOVED;
+    }
+    OrderMoveDomScope* domScopep() const { return m_domScopep; }
+    OrderMoveVertex* pomWaitingNextp() const { return m_pomWaitingE.nextp(); }
+    void domScopep(OrderMoveDomScope* ds) { m_domScopep = ds; }
+};
+
+// Similar to OrderMoveVertex, but modified for threaded code generation.
+class MTaskMoveVertex final : public V3GraphVertex {
+    //  This could be more compact, since we know m_varp and m_logicp
+    //  cannot both be set. Each MTaskMoveVertex represents a logic node
+    //  or a var node, it can't be both.
+    OrderLogicVertex* const m_logicp;  // Logic represented by this vertex
+    const OrderEitherVertex* const m_varp;  // Var represented by this vertex
+    const AstSenTree* const m_domainp;
+
+public:
+    MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp,
+                    const AstSenTree* domainp)
+        : V3GraphVertex{graphp}
+        , m_logicp{logicp}
+        , m_varp{varp}
+        , m_domainp{domainp} {
+        UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n");
+    }
+    ~MTaskMoveVertex() override = default;
+
+    // ACCESSORS
+    OrderLogicVertex* logicp() const { return m_logicp; }
+    const OrderEitherVertex* varp() const { return m_varp; }
+    const AstScope* scopep() const { return m_logicp ? m_logicp->scopep() : nullptr; }
+    const AstSenTree* domainp() const { return m_domainp; }
+
+    string dotColor() const override {
+        if (logicp()) {
+            return logicp()->dotColor();
+        } else {
+            return "yellow";
+        }
+    }
+    string name() const override {
+        string nm;
+        if (logicp()) {
+            nm = logicp()->name();
+            nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp()) + " s="
+                   + cvtToHex(logicp()->scopep())
+                   // "color()" represents the mtask ID.
+                   + "\\nt=" + cvtToStr(color()));
+        } else {
+            nm = "nolog\\nt=" + cvtToStr(color());
+        }
+        return nm;
+    }
+};
+
+#endif  // Guard
diff --git a/src/V3Param.cpp b/src/V3Param.cpp
index 18dfc06b6..b1153931b 100644
--- a/src/V3Param.cpp
+++ b/src/V3Param.cpp
@@ -801,6 +801,15 @@ class ParamProcessor final {
             srcModpr = modInfop->m_modp;
         }
 
+        for (auto* stmtp = srcModpr->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
+            if (auto* dtypep = VN_CAST(stmtp, ParamTypeDType)) {
+                if (VN_IS(dtypep->subDTypep(), VoidDType)) {
+                    nodep->v3error("Missing type parameter: " << dtypep->prettyNameQ());
+                    VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
+                }
+            }
+        }
+
         // Delete the parameters from the cell; they're not relevant any longer.
         if (paramsp) paramsp->unlinkFrBackWithNext()->deleteTree();
         return any_overrides;
@@ -822,8 +831,11 @@ class ParamProcessor final {
     }
 
     void classRefDeparam(AstClassRefDType* nodep, AstNodeModule*& srcModpr) {
-        if (nodeDeparamCommon(nodep, srcModpr, nodep->paramsp(), nullptr, false))
-            nodep->classp(VN_AS(srcModpr, Class));
+        if (nodeDeparamCommon(nodep, srcModpr, nodep->paramsp(), nullptr, false)) {
+            AstClass* const classp = VN_AS(srcModpr, Class);
+            nodep->classp(classp);
+            nodep->classOrPackagep(classp);
+        }
     }
 
 public:
@@ -915,20 +927,22 @@ class ParamVisitor final : public VNVisitor {
             // Process interface cells, then non-interface cells, which may reference an interface
             // cell.
             while (!m_cellps.empty()) {
-                const auto itm = m_cellps.cbegin();
-                AstNode* const cellp = itm->second;
-                m_cellps.erase(itm);
+                const auto itim = m_cellps.cbegin();
+                AstNode* const cellp = itim->second;
+                m_cellps.erase(itim);
 
                 AstNodeModule* srcModp = nullptr;
                 if (const auto* modCellp = VN_CAST(cellp, Cell)) {
                     srcModp = modCellp->modp();
                 } else if (const auto* classRefp = VN_CAST(cellp, ClassOrPackageRef)) {
                     srcModp = classRefp->classOrPackagep();
+                    if (VN_IS(classRefp->classOrPackageNodep(), ParamTypeDType)) continue;
                 } else if (const auto* classRefp = VN_CAST(cellp, ClassRefDType)) {
                     srcModp = classRefp->classp();
                 } else {
                     cellp->v3fatalSrc("Expected module parametrization");
                 }
+                UASSERT_OBJ(srcModp, cellp, "Unlinked class ref");
 
                 // Update path
                 string someInstanceName(modp->someInstanceName());
diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp
index 984544d82..7781f6eb8 100644
--- a/src/V3ParseGrammar.cpp
+++ b/src/V3ParseGrammar.cpp
@@ -184,7 +184,8 @@ AstVar* V3ParseGrammar::createVariable(FileLine* fileline, const string& name,
         }
     }
     if (type == VVarType::GENVAR) {
-        if (arrayp) fileline->v3error("Genvars may not be arrayed: " << name);
+        // Should be impossible as the grammer blocks this, but...
+        if (arrayp) fileline->v3error("Genvars may not be arrayed: " << name);  // LCOV_EXCL_LINE
     }
 
     // Split RANGE0-RANGE1-RANGE2 into
diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h
index 54c4ab080..9e3303923 100644
--- a/src/V3ParseImp.h
+++ b/src/V3ParseImp.h
@@ -232,8 +232,8 @@ public:
         m_stringps.push_back(strp);
         return strp;
     }
-    V3Number* newNumber(FileLine* fl, const char* text) {
-        V3Number* nump = new V3Number(V3Number::FileLined(), fl, text);
+    V3Number* newNumber(FileLine* flp, const char* text) {
+        V3Number* nump = new V3Number(flp, text);
         m_numberps.push_back(nump);
         return nump;
     }
diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp
index 18952bfe8..a9106d549 100644
--- a/src/V3Partition.cpp
+++ b/src/V3Partition.cpp
@@ -400,11 +400,8 @@ private:
 
 // Sort AbstractMTask objects into deterministic order by calling id()
 // which is a unique and stable serial number.
-class MTaskIdLessThan final {
-public:
-    MTaskIdLessThan() = default;
-    virtual ~MTaskIdLessThan() = default;
-    virtual bool operator()(const AbstractMTask* lhsp, const AbstractMTask* rhsp) const {
+struct MTaskIdLessThan final {
+    bool operator()(const AbstractMTask* lhsp, const AbstractMTask* rhsp) const {
         return lhsp->id() < rhsp->id();
     }
 };
@@ -735,20 +732,6 @@ void MergeCandidate::rescore() {
     }
 }
 
-// ######################################################################
-//  Vertex utility classes
-
-class OrderByPtrId final {
-    PartPtrIdMap m_ids;
-
-public:
-    virtual bool operator()(const OrderVarStdVertex* lhsp, const OrderVarStdVertex* rhsp) const {
-        const uint64_t l_id = m_ids.findId(lhsp);
-        const uint64_t r_id = m_ids.findId(rhsp);
-        return l_id < r_id;
-    }
-};
-
 //######################################################################
 // PartParallelismEst - Estimate parallelism of graph
 
@@ -945,7 +928,7 @@ class PartPropagateCp final {
 
 public:
     // CONSTRUCTORS
-    PartPropagateCp(bool slowAsserts)
+    explicit PartPropagateCp(bool slowAsserts)
         : m_slowAsserts{slowAsserts} {}
 
     // METHODS
@@ -1283,6 +1266,18 @@ public:
 
     // METHODS
     void go() {
+        if (m_slowAsserts) {
+            // Check there are no redundant edges
+            for (V3GraphVertex* itp = m_mtasksp->verticesBeginp(); itp;
+                 itp = itp->verticesNextp()) {
+                std::unordered_set neighbors;
+                for (V3GraphEdge* edgep = itp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+                    const bool first = neighbors.insert(edgep->top()).second;
+                    UASSERT_OBJ(first, itp, "Redundant edge found in input to PartContraction()");
+                }
+            }
+        }
+
         unsigned maxMTasks = v3Global.opt.threadsMaxMTasks();
         if (maxMTasks == 0) {  // Unspecified so estimate
             if (v3Global.opt.threads() > 1) {
@@ -1303,15 +1298,9 @@ public:
         //  - Incrementally recompute critical paths near the merged mtask.
 
         for (V3GraphVertex* itp = m_mtasksp->verticesBeginp(); itp; itp = itp->verticesNextp()) {
-            itp->userp(nullptr);  // Reset user value. Used by PartPropagateCp.
-            std::unordered_set neighbors;
+            itp->userp(nullptr);  // Reset user value while we are here. Used by PartPropagateCp.
             for (V3GraphEdge* edgep = itp->outBeginp(); edgep; edgep = edgep->outNextp()) {
                 m_sb.add(static_cast(edgep));
-                if (m_slowAsserts) {
-                    UASSERT_OBJ(neighbors.find(edgep->top()) == neighbors.end(), itp,
-                                "Redundant edge found in input to PartContraction()");
-                }
-                neighbors.insert(edgep->top());
             }
             siblingPairFromRelatives(itp);
             siblingPairFromRelatives(itp);
@@ -1962,103 +1951,78 @@ private:
 class PartFixDataHazards final {
 private:
     // TYPES
-    using LogicMTaskSet = std::set;
-    using TasksByRank = std::map;
-    using OvvSet = std::set;
-    using Olv2MTaskMap = std::unordered_map;
+    using TasksByRank = std::map>;
 
     // MEMBERS
+    const OrderGraph* const m_orderGraphp;  // The OrderGraph
     V3Graph* const m_mtasksp;  // Mtask graph
-    Olv2MTaskMap m_olv2mtask;  // Map OrderLogicVertex to LogicMTask who wraps it
-    unsigned m_mergesDone = 0;  // Number of MTasks merged. For stats only.
 public:
     // CONSTRUCTORs
-    explicit PartFixDataHazards(V3Graph* mtasksp)
-        : m_mtasksp{mtasksp} {}
+    explicit PartFixDataHazards(const OrderGraph* orderGraphp, V3Graph* mtasksp)
+        : m_orderGraphp{orderGraphp}
+        , m_mtasksp{mtasksp} {}
     // METHODS
 private:
-    void findAdjacentTasks(OvvSet::iterator ovvIt, TasksByRank* tasksByRankp) {
+    void findAdjacentTasks(const OrderVarStdVertex* varVtxp, TasksByRank& tasksByRank) {
         // Find all writer tasks for this variable, group by rank.
-        for (V3GraphEdge* edgep = (*ovvIt)->inBeginp(); edgep; edgep = edgep->inNextp()) {
-            const OrderLogicVertex* const logicp = dynamic_cast(edgep->fromp());
-            if (!logicp) continue;
-            if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue;
-            LogicMTask* const writerMtaskp = m_olv2mtask.at(logicp);
-            (*tasksByRankp)[writerMtaskp->rank()].insert(writerMtaskp);
+        for (V3GraphEdge* edgep = varVtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+            if (const auto* const logicVtxp = dynamic_cast(edgep->fromp())) {
+                LogicMTask* const writerMtaskp = static_cast(logicVtxp->userp());
+                tasksByRank[writerMtaskp->rank()].insert(writerMtaskp);
+            }
         }
         // Find all reader tasks for this variable, group by rank.
-        for (V3GraphEdge* edgep = (*ovvIt)->outBeginp(); edgep; edgep = edgep->outNextp()) {
-            const OrderLogicVertex* const logicp = dynamic_cast(edgep->fromp());
-            if (!logicp) continue;
-            if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue;
-            LogicMTask* const readerMtaskp = m_olv2mtask.at(logicp);
-            (*tasksByRankp)[readerMtaskp->rank()].insert(readerMtaskp);
+        for (V3GraphEdge* edgep = varVtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+            if (const auto* const logicVtxp = dynamic_cast(edgep->fromp())) {
+                LogicMTask* const readerMtaskp = static_cast(logicVtxp->userp());
+                tasksByRank[readerMtaskp->rank()].insert(readerMtaskp);
+            }
         }
     }
-    void mergeSameRankTasks(TasksByRank* tasksByRankp) {
-        LogicMTask* lastMergedp = nullptr;
-        for (TasksByRank::iterator rankIt = tasksByRankp->begin(); rankIt != tasksByRankp->end();
-             ++rankIt) {
+    void mergeSameRankTasks(const TasksByRank& tasksByRank) {
+        LogicMTask* lastRecipientp = nullptr;
+        for (const auto& pair : tasksByRank) {
             // Find the largest node at this rank, merge into it.  (If we
             // happen to find a huge node, this saves time in
             // partRedirectEdgesFrom() versus merging into an arbitrary node.)
-            LogicMTask* mergedp = nullptr;
-            for (LogicMTaskSet::iterator it = rankIt->second.begin(); it != rankIt->second.end();
-                 ++it) {
-                LogicMTask* const mtaskp = *it;
-                if (mergedp) {
-                    if (mergedp->cost() < mtaskp->cost()) mergedp = mtaskp;
-                } else {
-                    mergedp = mtaskp;
-                }
+            LogicMTask* recipientp = nullptr;
+            for (LogicMTask* const mtaskp : pair.second) {
+                if (!recipientp || (recipientp->cost() < mtaskp->cost())) recipientp = mtaskp;
             }
-            rankIt->second.erase(mergedp);
+            UASSERT_OBJ(!lastRecipientp || (lastRecipientp->rank() < recipientp->rank()),
+                        recipientp, "Merging must be on lower rank");
 
-            while (!rankIt->second.empty()) {
-                const auto begin = rankIt->second.cbegin();
-                LogicMTask* const donorp = *begin;
-                UASSERT_OBJ(donorp != mergedp, donorp, "Donor can't be merged edge");
-                rankIt->second.erase(begin);
-                // Merge donorp into mergedp.
-                // Fix up the map, so donor's OLVs map to mergedp
-                for (LogicMTask::VxList::const_iterator tmvit = donorp->vertexListp()->begin();
-                     tmvit != donorp->vertexListp()->end(); ++tmvit) {
-                    const MTaskMoveVertex* const tmvp = *tmvit;
-                    const OrderLogicVertex* const logicp = tmvp->logicp();
-                    if (logicp) m_olv2mtask[logicp] = mergedp;
+            for (LogicMTask* const donorp : pair.second) {
+                // Merge donor into recipient.
+                if (donorp == recipientp) continue;
+                // Fix up the map, so donor's OLVs map to recipientp
+                for (const MTaskMoveVertex* const tmvp : *(donorp->vertexListp())) {
+                    tmvp->logicp()->userp(recipientp);
                 }
-                // Move all vertices from donorp to mergedp
-                mergedp->moveAllVerticesFrom(donorp);
+                // Move all vertices from donorp to recipientp
+                recipientp->moveAllVerticesFrom(donorp);
                 // Redirect edges from donorp to recipientp, delete donorp
-                partRedirectEdgesFrom(m_mtasksp, mergedp, donorp, nullptr);
-                ++m_mergesDone;
+                partRedirectEdgesFrom(m_mtasksp, recipientp, donorp, nullptr);
             }
 
-            if (lastMergedp) {
-                UASSERT_OBJ(lastMergedp->rank() < mergedp->rank(), mergedp,
-                            "Merging must be on lower rank");
-                if (!lastMergedp->hasRelativeMTask(mergedp)) {
-                    new MTaskEdge(m_mtasksp, lastMergedp, mergedp, 1);
-                }
+            if (lastRecipientp && !lastRecipientp->hasRelativeMTask(recipientp)) {
+                new MTaskEdge{m_mtasksp, lastRecipientp, recipientp, 1};
             }
-            lastMergedp = mergedp;
+            lastRecipientp = recipientp;
         }
     }
     bool hasDpiHazard(LogicMTask* mtaskp) {
-        for (LogicMTask::VxList::const_iterator it = mtaskp->vertexListp()->begin();
-             it != mtaskp->vertexListp()->end(); ++it) {
-            if (!(*it)->logicp()) continue;
-            AstNode* const nodep = (*it)->logicp()->nodep();
-            // NOTE: We don't handle DPI exports. If testbench code calls a
-            // DPI-exported function at any time during eval() we may have
-            // a data hazard. (Likewise in non-threaded mode if an export
-            // messes with an ordered variable we're broken.)
+        for (const MTaskMoveVertex* const moveVtxp : *(mtaskp->vertexListp())) {
+            if (OrderLogicVertex* const lvtxp = moveVtxp->logicp()) {
+                // NOTE: We don't handle DPI exports. If testbench code calls a
+                // DPI-exported function at any time during eval() we may have
+                // a data hazard. (Likewise in non-threaded mode if an export
+                // messes with an ordered variable we're broken.)
 
-            // Find all calls to DPI-imported functions, we can put those
-            // into a serial order at least. That should solve the most
-            // likely DPI-related data hazards.
-            if (DpiImportCallVisitor(nodep).hasDpiHazard()) {  //
-                return true;
+                // Find all calls to DPI-imported functions, we can put those
+                // into a serial order at least. That should solve the most
+                // likely DPI-related data hazards.
+                if (DpiImportCallVisitor{lvtxp->nodep()}.hasDpiHazard()) return true;
             }
         }
         return false;
@@ -2066,56 +2030,44 @@ private:
 
 public:
     void go() {
-        uint64_t startUsecs = 0;
-        if (debug() >= 3) startUsecs = V3Os::timeUsecs();
-
-        // Build an OLV->mtask map and a set of OVVs
-        OrderByPtrId ovvOrder;
-        OvvSet ovvSet(ovvOrder);
-        // OVV's which wrap systemC vars will be handled slightly specially
-        OvvSet ovvSetSystemC(ovvOrder);
-
-        for (V3GraphVertex* vxp = m_mtasksp->verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
-            LogicMTask* const mtaskp = static_cast(vxp);
-            // Should be only one MTaskMoveVertex in each mtask at this
-            // stage, but whatever, write it as a loop:
-            for (LogicMTask::VxList::const_iterator it = mtaskp->vertexListp()->begin();
-                 it != mtaskp->vertexListp()->end(); ++it) {
-                const MTaskMoveVertex* const tmvp = *it;
-                if (const OrderLogicVertex* const logicp = tmvp->logicp()) {
-                    m_olv2mtask[logicp] = mtaskp;
-                    // Look at downstream vars.
-                    for (V3GraphEdge* edgep = logicp->outBeginp(); edgep;
-                         edgep = edgep->outNextp()) {
-                        // Only consider OrderVarStdVertex which reflects
-                        // an actual lvalue assignment; the others do not.
-                        const OrderVarStdVertex* const ovvp
-                            = dynamic_cast(edgep->top());
-                        if (!ovvp) continue;
-                        if (ovvp->varScp()->varp()->isSc()) {
-                            ovvSetSystemC.insert(ovvp);
-                        } else {
-                            ovvSet.insert(ovvp);
-                        }
-                    }
+        // Rank the graph. DGS is faster than V3GraphAlg's recursive rank, and also allows us to
+        // set up the OrderLogicVertex -> LogicMTask map at the same time.
+        {
+            GraphStreamUnordered serialize(m_mtasksp);
+            while (LogicMTask* const mtaskp
+                   = const_cast(static_cast(serialize.nextp()))) {
+                // Compute and assign rank
+                uint32_t rank = 0;
+                for (V3GraphEdge* edgep = mtaskp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+                    rank = std::max(edgep->fromp()->rank() + 1, rank);
                 }
+                mtaskp->rank(rank);
+
+                // Set up the OrderLogicVertex -> LogicMTask map
+                // Entry and exit MTasks have no MTaskMoveVertices under them, so move on
+                if (mtaskp->vertexListp()->empty()) continue;
+                // Otherwise there should be only one MTaskMoveVertex in each MTask at this stage
+                UASSERT_OBJ(mtaskp->vertexListp()->size() == 1, mtaskp,
+                            "Multiple MTaskMoveVertex");
+                const MTaskMoveVertex* const moveVtxp = mtaskp->vertexListp()->front();
+                // Set up mapping back to the MTask from the OrderLogicVertex
+                if (OrderLogicVertex* const lvtxp = moveVtxp->logicp()) lvtxp->userp(mtaskp);
             }
         }
 
-        // Rank the graph.
-        // DGS is faster than V3GraphAlg's recursive rank, in the worst
-        // cases where the recursive rank must pass through the same node
-        // many times. (We saw 22s for DGS vs. 500s for recursive rank on
-        // one large design.)
-        {
-            GraphStreamUnordered serialize(m_mtasksp);
-            const V3GraphVertex* vertexp;
-            while ((vertexp = serialize.nextp())) {
-                uint32_t rank = 0;
-                for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
-                    rank = std::max(edgep->fromp()->rank() + 1, rank);
+        // Gather all variables. SystemC vars will be handled slightly specially, so keep separate.
+        std::vector regularVars;
+        std::vector systemCVars;
+        for (V3GraphVertex *vtxp = m_orderGraphp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+            nextp = vtxp->verticesNextp();
+            // Only consider OrderVarStdVertex which reflects
+            // an actual lvalue assignment; the others do not.
+            if (const OrderVarStdVertex* const vvtxp = dynamic_cast(vtxp)) {
+                if (vvtxp->vscp()->varp()->isSc()) {
+                    systemCVars.push_back(vvtxp);
+                } else {
+                    regularVars.push_back(vvtxp);
                 }
-                const_cast(vertexp)->rank(rank);
             }
         }
 
@@ -2132,14 +2084,14 @@ public:
         // NOTE: we don't update the CP's stored in the LogicMTasks to
         // reflect the changes we make to the graph. That's OK, as we
         // haven't yet initialized CPs when we call this routine.
-        for (OvvSet::iterator ovvit = ovvSet.begin(); ovvit != ovvSet.end(); ++ovvit) {
+        for (const OrderVarStdVertex* const varVtxp : regularVars) {
             // Build a set of mtasks, per rank, which access this var.
             // Within a rank, sort by MTaskID to avoid nondeterminism.
             TasksByRank tasksByRank;
 
             // Find all reader and writer tasks for this variable, add to
             // tasksByRank.
-            findAdjacentTasks(ovvit, &tasksByRank);
+            findAdjacentTasks(varVtxp, tasksByRank);
 
             // Merge all writer and reader tasks from same rank together.
             //
@@ -2156,7 +2108,7 @@ public:
             // and it seems to.  It also creates fairly few edges. We don't
             // want to create tons of edges here, doing so is not nice to
             // the main edge contraction pass.
-            mergeSameRankTasks(&tasksByRank);
+            mergeSameRankTasks(tasksByRank);
         }
 
         // Handle SystemC vars just a little differently. Instead of
@@ -2172,11 +2124,10 @@ public:
         // Hopefully we only have a few SC vars -- top level ports, probably.
         {
             TasksByRank tasksByRank;
-            for (OvvSet::iterator ovvit = ovvSetSystemC.begin(); ovvit != ovvSetSystemC.end();
-                 ++ovvit) {
-                findAdjacentTasks(ovvit, &tasksByRank);
+            for (const OrderVarStdVertex* const varVtxp : systemCVars) {
+                findAdjacentTasks(varVtxp, tasksByRank);
             }
-            mergeSameRankTasks(&tasksByRank);
+            mergeSameRankTasks(tasksByRank);
         }
 
         // Handle nodes containing DPI calls, we want to serialize those
@@ -2184,17 +2135,13 @@ public:
         // Same basic strategy as above to serialize access to SC vars.
         if (!v3Global.opt.threadsDpiPure() || !v3Global.opt.threadsDpiUnpure()) {
             TasksByRank tasksByRank;
-            for (V3GraphVertex* vxp = m_mtasksp->verticesBeginp(); vxp;
-                 vxp = vxp->verticesNextp()) {
-                LogicMTask* const mtaskp = static_cast(vxp);
-                if (hasDpiHazard(mtaskp)) tasksByRank[vxp->rank()].insert(mtaskp);
+            for (V3GraphVertex *vtxp = m_mtasksp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+                nextp = vtxp->verticesNextp();
+                LogicMTask* const mtaskp = static_cast(vtxp);
+                if (hasDpiHazard(mtaskp)) tasksByRank[mtaskp->rank()].insert(mtaskp);
             }
-            mergeSameRankTasks(&tasksByRank);
+            mergeSameRankTasks(tasksByRank);
         }
-
-        UINFO(4, "PartFixDataHazards() merged " << m_mergesDone << " pairs of nodes in "
-                                                << (V3Os::timeUsecs() - startUsecs)
-                                                << " usecs.\n");
     }
 
 private:
@@ -2660,33 +2607,126 @@ void V3Partition::hashGraphDebug(const V3Graph* graphp, const char* debugName) {
     UINFO(0, "Hash of shape (not contents) of " << debugName << " = " << cvtToStr(hash) << endl);
 }
 
-void V3Partition::setupMTaskDeps(V3Graph* mtasksp, const Vx2MTaskMap* vx2mtaskp) {
-    // Look at each mtask
-    for (V3GraphVertex* itp = mtasksp->verticesBeginp(); itp; itp = itp->verticesNextp()) {
-        LogicMTask* const mtaskp = static_cast(itp);
-        const LogicMTask::VxList* vertexListp = mtaskp->vertexListp();
+// Predicate function to determine what MTaskMoveVertex to bypass when constructing the MTask
+// graph. The fine-grained dependency graph of MTaskMoveVertex vertices is a bipartite graph of:
+// - 1. MTaskMoveVertex instances containing logic via OrderLogicVertex
+//      (MTaskMoveVertex::logicp() != nullptr)
+// - 2. MTaskMoveVertex instances containing an (OrderVarVertex, domain) pair
+// Our goal is to order the logic vertices. The second type of variable/domain vertices only carry
+// dependencies and are eventually discarded. In order to reduce the working set size of
+// PartContraction, we 'bypass' and not create LogicMTask vertices for the variable vertices, and
+// instead add the transitive dependencies directly, but only if adding the transitive edges
+// directly does not require more dependency edges than keeping the intermediate vertex. That is,
+// we bypass a variable vertex if fanIn * fanOut <= fanIn + fanOut. This can only be true if fanIn
+// or fanOut are 1, or if they are both 2. This can cause significant reduction in working set
+// size.
+static bool bypassOk(MTaskMoveVertex* mvtxp) {
+    // Need to keep all logic vertices
+    if (mvtxp->logicp()) return false;
+    // Count fan-in, up to 3
+    unsigned fanIn = 0;
+    for (V3GraphEdge* edgep = mvtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+        if (++fanIn == 3) break;
+    }
+    UDEBUGONLY(UASSERT_OBJ(fanIn <= 3, mvtxp, "Should have stopped counting fanIn"););
+    // If fanInn no more than one, bypass
+    if (fanIn <= 1) return true;
+    // Count fan-out, up to 3
+    unsigned fanOut = 0;
+    for (V3GraphEdge* edgep = mvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+        if (++fanOut == 3) break;
+    }
+    UDEBUGONLY(UASSERT_OBJ(fanOut <= 3, mvtxp, "Should have stopped counting fanOut"););
+    // If fan-out no more than one, bypass
+    if (fanOut <= 1) return true;
+    // They can only be (2, 2), (2, 3), (3, 2), (3, 3) at this point, bypass if (2, 2)
+    return fanIn + fanOut == 4;
+}
 
-        // For each logic vertex in this mtask, create an mtask-to-mtask
-        // edge based on the logic-to-logic edge.
-        for (LogicMTask::VxList::const_iterator vit = vertexListp->begin();
-             vit != vertexListp->end(); ++vit) {
-            for (V3GraphEdge* outp = (*vit)->outBeginp(); outp; outp = outp->outNextp()) {
-                UASSERT(outp->weight() > 0, "Mtask not assigned weight");
-                const MTaskMoveVertex* const top = dynamic_cast(outp->top());
-                UASSERT(top, "MoveVertex not associated to mtask");
-                const auto it = vlstd::as_const(vx2mtaskp)->find(top);
-                UASSERT(it != vx2mtaskp->end(), "MTask map can't find id");
-                LogicMTask* const otherMTaskp = it->second;
-                UASSERT(otherMTaskp, "nullptr other Mtask");
-                UASSERT_OBJ(otherMTaskp != mtaskp, mtaskp, "Would create a cycle edge");
+uint32_t V3Partition::setupMTaskDeps(V3Graph* mtasksp) {
+    uint32_t totalGraphCost = 0;
 
-                // Don't create redundant edges.
-                if (mtaskp->hasRelativeMTask(otherMTaskp)) continue;
+    // Artificial single entry point vertex in the MTask graph to allow sibling merges.
+    // This is required as otherwise disjoint sub-graphs could not be merged, but the
+    // coarsening algorithm assumes that the graph is connected.
+    LogicMTask* const entryMTask = new LogicMTask{mtasksp, nullptr};
 
-                new MTaskEdge(mtasksp, mtaskp, otherMTaskp, 1);
+    // The V3InstrCount within LogicMTask will set user5 on each AST
+    // node, to assert that we never count any node twice.
+    const VNUser5InUse user5inUse;
+
+    // Create the LogicMTasks for each MTaskMoveVertex
+    for (V3GraphVertex *vtxp = m_fineDepsGraphp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNextp();
+        MTaskMoveVertex* const mVtxp = static_cast(vtxp);
+        if (bypassOk(mVtxp)) {
+            mVtxp->userp(nullptr);  // Set to nullptr to mark as bypassed
+        } else {
+            LogicMTask* const mtaskp = new LogicMTask{mtasksp, mVtxp};
+            mVtxp->userp(mtaskp);
+            totalGraphCost += mtaskp->cost();
+        }
+    }
+
+    // Artificial single exit point vertex in the MTask graph to allow sibling merges.
+    // this enables merging MTasks with no downstream dependents if that is the ideal merge.
+    LogicMTask* const exitMTask = new LogicMTask{mtasksp, nullptr};
+
+    // Create the mtask->mtask dependency edges based on the dependencies between MTaskMoveVertex
+    // vertices.
+    for (V3GraphVertex *vtxp = mtasksp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNextp();
+        LogicMTask* const mtaskp = static_cast(vtxp);
+
+        // Entry and exit vertices handled separately
+        if (VL_UNLIKELY((mtaskp == entryMTask) || (mtaskp == exitMTask))) continue;
+
+        // At this point, there should only be one MTaskMoveVertex per LogicMTask
+        UASSERT_OBJ(mtaskp->vertexListp()->size() == 1, mtaskp, "Multiple MTaskMoveVertex");
+        MTaskMoveVertex* const mvtxp = mtaskp->vertexListp()->front();
+        UASSERT_OBJ(mvtxp->userp(), mtaskp, "Bypassed MTaskMoveVertex should not have MTask");
+
+        // Function to add a edge to a dependent from 'mtaskp'
+        const auto addEdge = [mtasksp, mtaskp](LogicMTask* otherp) {
+            UASSERT_OBJ(otherp != mtaskp, mtaskp, "Would create a cycle edge");
+            if (mtaskp->hasRelativeMTask(otherp)) return;  // Don't create redundant edges.
+            new MTaskEdge{mtasksp, mtaskp, otherp, 1};
+        };
+
+        // Iterate downstream direct dependents
+        for (V3GraphEdge *dEdgep = mvtxp->outBeginp(), *dNextp; dEdgep; dEdgep = dNextp) {
+            dNextp = dEdgep->outNextp();
+            V3GraphVertex* const top = dEdgep->top();
+            if (LogicMTask* const otherp = static_cast(top->userp())) {
+                // The opposite end of the edge is not a bypassed vertex, add as direct dependent
+                addEdge(otherp);
+            } else {
+                // The opposite end of the edge is a bypassed vertex, add transitive dependents
+                for (V3GraphEdge *tEdgep = top->outBeginp(), *tNextp; tEdgep; tEdgep = tNextp) {
+                    tNextp = tEdgep->outNextp();
+                    LogicMTask* const transp = static_cast(tEdgep->top()->userp());
+                    // The Move graph is bipartite (logic <-> var), and logic is never bypassed,
+                    // hence 'transp' must be non nullptr.
+                    UASSERT_OBJ(transp, mvtxp, "This cannot be a bypassed vertex");
+                    addEdge(transp);
+                }
             }
         }
     }
+
+    // Create Dependencies to/from the entry/exit vertices.
+    for (V3GraphVertex *vtxp = mtasksp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) {
+        nextp = vtxp->verticesNextp();
+        LogicMTask* const mtaskp = static_cast(vtxp);
+
+        if (VL_UNLIKELY((mtaskp == entryMTask) || (mtaskp == exitMTask))) continue;
+
+        // Add the entry/exit edges
+        if (mtaskp->inEmpty()) new MTaskEdge{mtasksp, entryMTask, mtaskp, 1};
+        if (mtaskp->outEmpty()) new MTaskEdge{mtasksp, mtaskp, exitMTask, 1};
+    }
+
+    return totalGraphCost;
 }
 
 void V3Partition::go(V3Graph* mtasksp) {
@@ -2697,26 +2737,7 @@ void V3Partition::go(V3Graph* mtasksp) {
     // MTaskMoveVertex. Over time, we'll merge MTasks together and
     // eventually each MTask will wrap a large number of MTaskMoveVertices
     // (and the logic nodes therein.)
-    uint32_t totalGraphCost = 0;
-    {
-        // The V3InstrCount within LogicMTask will set user5 on each AST
-        // node, to assert that we never count any node twice.
-        const VNUser5InUse inUser5;
-        Vx2MTaskMap vx2mtask;
-        for (V3GraphVertex* vxp = m_fineDepsGraphp->verticesBeginp(); vxp;
-             vxp = vxp->verticesNextp()) {
-            MTaskMoveVertex* const mtmvVxp = dynamic_cast(vxp);
-            UASSERT_OBJ(mtmvVxp, vxp, "Every vertex here should be an MTaskMoveVertex");
-
-            LogicMTask* const mtaskp = new LogicMTask(mtasksp, mtmvVxp);
-            vx2mtask[mtmvVxp] = mtaskp;
-
-            totalGraphCost += mtaskp->cost();
-        }
-
-        // Create the mtask->mtask dep edges based on vertex deps
-        setupMTaskDeps(mtasksp, &vx2mtask);
-    }
+    const uint32_t totalGraphCost = setupMTaskDeps(mtasksp);
 
     V3Partition::debugMTaskGraphStats(mtasksp, "initial");
 
@@ -2727,7 +2748,7 @@ void V3Partition::go(V3Graph* mtasksp) {
 
     // Merge nodes that could present data hazards; see comment within.
     {
-        PartFixDataHazards(mtasksp).go();
+        PartFixDataHazards(m_orderGraphp, mtasksp).go();
         V3Partition::debugMTaskGraphStats(mtasksp, "hazards");
         hashGraphDebug(mtasksp, "mtasksp after fixDataHazards()");
     }
@@ -3121,15 +3142,15 @@ static const std::vector createThreadFunctions(const ThreadSchedule&
         }
 
         // Unblock the fake "final" mtask when this thread is finished
-        funcp->addStmtsp(
-            new AstCStmt(fl, "vlSelf->__Vm_mtaskstate_final.signalUpstreamDone(even_cycle);\n"));
+        funcp->addStmtsp(new AstCStmt{fl, "vlSelf->__Vm_mtaskstate_final__" + tag
+                                              + ".signalUpstreamDone(even_cycle);\n"});
     }
 
     // Create the fake "final" mtask state variable
     AstBasicDType* const mtaskStateDtypep
         = v3Global.rootp()->typeTablep()->findBasicDType(fl, VBasicDTypeKwd::MTASKSTATE);
     AstVar* const varp
-        = new AstVar(fl, VVarType::MODULETEMP, "__Vm_mtaskstate_final", mtaskStateDtypep);
+        = new AstVar{fl, VVarType::MODULETEMP, "__Vm_mtaskstate_final__" + tag, mtaskStateDtypep};
     varp->valuep(new AstConst(fl, funcps.size()));
     varp->protect(false);  // Do not protect as we still have references in AstText
     modp->addStmtsp(varp);
@@ -3141,6 +3162,7 @@ static void addThreadStartToExecGraph(AstExecGraph* const execGraphp,
                                       const std::vector& funcps) {
     // FileLine used for constructing nodes below
     FileLine* const fl = v3Global.rootp()->fileline();
+    const string& tag = execGraphp->name();
 
     // Add thread function invocations to execGraph
     const auto addStrStmt = [=](const string& stmt) -> void {  //
@@ -3150,7 +3172,8 @@ static void addThreadStartToExecGraph(AstExecGraph* const execGraphp,
         execGraphp->addStmtsp(new AstText(fl, text, /* tracking: */ true));
     };
 
-    addStrStmt("vlSymsp->__Vm_even_cycle = !vlSymsp->__Vm_even_cycle;\n");
+    addStrStmt("vlSymsp->__Vm_even_cycle__" + tag + " = !vlSymsp->__Vm_even_cycle__" + tag
+               + ";\n");
 
     const uint32_t last = funcps.size() - 1;
     for (uint32_t i = 0; i <= last; ++i) {
@@ -3159,17 +3182,18 @@ static void addThreadStartToExecGraph(AstExecGraph* const execGraphp,
             // The first N-1 will run on the thread pool.
             addTextStmt("vlSymsp->__Vm_threadPoolp->workerp(" + cvtToStr(i) + ")->addTask(");
             execGraphp->addStmtsp(new AstAddrOfCFunc(fl, funcp));
-            addTextStmt(", vlSelf, vlSymsp->__Vm_even_cycle);\n");
+            addTextStmt(", vlSelf, vlSymsp->__Vm_even_cycle__" + tag + ");\n");
         } else {
             // The last will run on the main thread.
             AstCCall* const callp = new AstCCall(fl, funcp);
-            callp->argTypes("vlSelf, vlSymsp->__Vm_even_cycle");
+            callp->argTypes("vlSelf, vlSymsp->__Vm_even_cycle__" + tag);
             execGraphp->addStmtsp(callp);
             addStrStmt("Verilated::mtaskId(0);\n");
         }
     }
 
-    addStrStmt("vlSelf->__Vm_mtaskstate_final.waitUntilUpstreamDone(vlSymsp->__Vm_even_cycle);\n");
+    addStrStmt("vlSelf->__Vm_mtaskstate_final__" + tag
+               + ".waitUntilUpstreamDone(vlSymsp->__Vm_even_cycle__" + tag + ");\n");
 }
 
 static void implementExecGraph(AstExecGraph* const execGraphp) {
@@ -3191,7 +3215,7 @@ static void implementExecGraph(AstExecGraph* const execGraphp) {
 
 void V3Partition::finalize(AstNetlist* netlistp) {
     // Called by Verilator top stage
-    netlistp->topModulep()->foreach([&](AstExecGraph* execGraphp) {
+    netlistp->topModulep()->foreach([&](AstExecGraph* execGraphp) {
         // Back in V3Order, we partitioned mtasks using provisional cost
         // estimates. However, V3Order precedes some optimizations (notably
         // V3LifePost) that can change the cost of logic within each mtask.
diff --git a/src/V3Partition.h b/src/V3Partition.h
index fc410c5c0..72a9ed43f 100644
--- a/src/V3Partition.h
+++ b/src/V3Partition.h
@@ -22,6 +22,7 @@
 
 #include "V3Graph.h"
 #include "V3OrderGraph.h"
+#include "V3OrderMoveGraph.h"
 
 #include 
 #include 
@@ -37,11 +38,13 @@ using Vx2MTaskMap = std::unordered_map;
 
 class V3Partition final {
     // MEMBERS
-    V3Graph* const m_fineDepsGraphp;  // Fine-grained dependency graph
+    const OrderGraph* const m_orderGraphp;  // The OrderGraph
+    const V3Graph* const m_fineDepsGraphp;  // Fine-grained dependency graph
 public:
     // CONSTRUCTORS
-    explicit V3Partition(V3Graph* fineDepsGraphp)
-        : m_fineDepsGraphp{fineDepsGraphp} {}
+    explicit V3Partition(const OrderGraph* orderGraphp, const V3Graph* fineDepsGraphp)
+        : m_orderGraphp{orderGraphp}
+        , m_fineDepsGraphp{fineDepsGraphp} {}
     ~V3Partition() = default;
 
     // METHODS
@@ -65,7 +68,7 @@ public:
     static void finalize(AstNetlist* netlistp);
 
 private:
-    static void setupMTaskDeps(V3Graph* mtasksp, const Vx2MTaskMap* vx2mtaskp);
+    uint32_t setupMTaskDeps(V3Graph* mtasksp);
 
     VL_UNCOPYABLE(V3Partition);
 };
diff --git a/src/V3PartitionGraph.h b/src/V3PartitionGraph.h
index d8ed603af..6f8e8c72d 100644
--- a/src/V3PartitionGraph.h
+++ b/src/V3PartitionGraph.h
@@ -21,7 +21,7 @@
 #include "verilatedos.h"
 
 #include "V3Graph.h"
-#include "V3OrderGraph.h"
+#include "V3OrderMoveGraph.h"
 
 #include 
 
diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp
index 48f8c5c31..5411dbf07 100644
--- a/src/V3Premit.cpp
+++ b/src/V3Premit.cpp
@@ -186,10 +186,10 @@ private:
             bool noopt = false;
             {
                 const VNUser3InUse user3InUse;
-                nodep->lhsp()->foreach([](const AstVarRef* refp) {
+                nodep->lhsp()->foreach([](const AstVarRef* refp) {
                     if (refp->access().isWriteOrRW()) refp->varp()->user3(true);
                 });
-                nodep->rhsp()->foreach([&noopt](const AstVarRef* refp) {
+                nodep->rhsp()->foreach([&noopt](const AstVarRef* refp) {
                     if (refp->access().isReadOnly() && refp->varp()->user3()) noopt = true;
                 });
             }
diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp
new file mode 100644
index 000000000..36a23771f
--- /dev/null
+++ b/src/V3Sched.cpp
@@ -0,0 +1,1070 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Code scheduling
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// V3Sched::schedule is the top level entry-point to the scheduling algorithm
+// at a high level, the process is:
+//
+//  - Gather and classify all logic in the design based on what triggers its execution
+//  - Schedule static, initial and final logic classes in source order
+//  - Break combinational cycles by introducing hybrid logic
+//  - Create 'settle' region that restores the combinational invariant
+//  - Partition the clocked and combinational (including hybrid) logic into pre/act/nba.
+//    All clocks (signals referenced in an AstSenTree) generated via a blocking assignment
+//    (including combinationally generated signals) are computed within the act region.
+//  - Replicate combinational logic
+//  - Create input combinational logic loop
+//  - Create the pre/act/nba triggers
+//  - Create the 'act' region evaluation function
+//  - Create the 'nba' region evaluation function
+//  - Bolt it all together to create the '_eval' function
+//
+// Details of the algorithm are described in the internals documentation docs/internals.rst
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Sched.h"
+
+#include "V3Ast.h"
+#include "V3EmitCBase.h"
+#include "V3EmitV.h"
+#include "V3Order.h"
+#include "V3SenExprBuilder.h"
+#include "V3Stats.h"
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+namespace V3Sched {
+
+namespace {
+
+//============================================================================
+// Utility functions
+
+AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow) {
+    AstScope* const scopeTopp = netlistp->topScopep()->scopep();
+    AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), name, scopeTopp, ""};
+    funcp->dontCombine(true);
+    funcp->isStatic(false);
+    funcp->isLoose(true);
+    funcp->slow(slow);
+    funcp->isConst(false);
+    funcp->declPrivate(true);
+    scopeTopp->addBlocksp(funcp);
+    return funcp;
+}
+
+AstCFunc* makeTopFunction(AstNetlist* netlistp, const string& name, bool slow) {
+    AstCFunc* const funcp = makeSubFunction(netlistp, name, slow);
+    funcp->entryPoint(true);
+    return funcp;
+}
+
+std::vector getSenTreesUsedBy(const std::vector& lbsps) {
+    const VNUser1InUse user1InUse;
+    std::vector result;
+    for (const LogicByScope* const lbsp : lbsps) {
+        for (const auto& pair : *lbsp) {
+            AstActive* const activep = pair.second;
+            AstSenTree* const senTreep = activep->sensesp();
+            if (senTreep->user1SetOnce()) continue;
+            if (senTreep->hasClocked() || senTreep->hasHybrid()) result.push_back(senTreep);
+        }
+    }
+    return result;
+}
+
+AstAssign* setVar(AstVarScope* vscp, uint32_t val) {
+    FileLine* const flp = vscp->fileline();
+    AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::WRITE};
+    AstConst* const valp = new AstConst{flp, AstConst::DTyped{}, vscp->dtypep()};
+    valp->num().setLong(val);
+    return new AstAssign{flp, refp, valp};
+};
+
+void remapSensitivities(const LogicByScope& lbs,
+                        std::unordered_map senTreeMap) {
+    for (const auto& pair : lbs) {
+        AstActive* const activep = pair.second;
+        AstSenTree* const senTreep = activep->sensesp();
+        if (senTreep->hasCombo()) continue;
+        activep->sensesp(senTreeMap.at(senTreep));
+    }
+}
+
+void invertAndMergeSenTreeMap(
+    std::unordered_map& result,
+    const std::unordered_map& senTreeMap) {
+    for (const auto& pair : senTreeMap) {
+        UASSERT_OBJ(!pair.second->sensesp()->nextp(), pair.second, "Should be single AstSenIem");
+        result.emplace(pair.second->sensesp(), pair.first);
+    }
+}
+
+//============================================================================
+// Split large function according to --output-split-cfuncs
+
+void splitCheck(AstCFunc* ofuncp) {
+    if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
+    if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
+
+    int funcnum = 0;
+    int func_stmts = 0;
+    AstCFunc* funcp = nullptr;
+
+    // Unlink all statements, then add item by item to new sub-functions
+    AstBegin* const tempp = new AstBegin{ofuncp->fileline(), "[EditWrapper]",
+                                         ofuncp->stmtsp()->unlinkFrBackWithNext()};
+    // Currently we do not use finalsp in V3Sched, if we do, it needs to be handled here
+    UASSERT_OBJ(!ofuncp->finalsp(), ofuncp, "Should not have any finalps");
+    while (tempp->stmtsp()) {
+        AstNode* const itemp = tempp->stmtsp()->unlinkFrBack();
+        const int stmts = itemp->nodeCount();
+        if (!funcp || (func_stmts + stmts) > v3Global.opt.outputSplitCFuncs()) {
+            // Make a new function
+            funcp = new AstCFunc{ofuncp->fileline(), ofuncp->name() + "__" + cvtToStr(funcnum++),
+                                 ofuncp->scopep()};
+            funcp->dontCombine(true);
+            funcp->isStatic(false);
+            funcp->isLoose(true);
+            funcp->slow(ofuncp->slow());
+            ofuncp->scopep()->addBlocksp(funcp);
+            //
+            AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
+            ofuncp->addStmtsp(callp);
+            func_stmts = 0;
+        }
+        funcp->addStmtsp(itemp);
+        func_stmts += stmts;
+    }
+    VL_DO_DANGLING(tempp->deleteTree(), tempp);
+}
+
+//============================================================================
+// Collect and classify all logic in the design
+
+LogicClasses gatherLogicClasses(AstNetlist* netlistp) {
+    LogicClasses result;
+
+    netlistp->foreach([&](AstScope* scopep) {
+        std::vector empty;
+
+        scopep->foreach([&](AstActive* activep) {
+            AstSenTree* const senTreep = activep->sensesp();
+            if (!activep->stmtsp()) {
+                // Some AstActives might be empty due to previous optimizations
+                empty.push_back(activep);
+            } else if (senTreep->hasStatic()) {
+                UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
+                            "static initializer with additional sensitivities");
+                result.m_static.emplace_back(scopep, activep);
+            } else if (senTreep->hasInitial()) {
+                UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
+                            "'initial' logic with additional sensitivities");
+                result.m_initial.emplace_back(scopep, activep);
+            } else if (senTreep->hasFinal()) {
+                UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
+                            "'final' logic with additional sensitivities");
+                result.m_final.emplace_back(scopep, activep);
+            } else if (senTreep->hasCombo()) {
+                UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep,
+                            "combinational logic with additional sensitivities");
+                if (VN_IS(activep->stmtsp(), AlwaysPostponed)) {
+                    result.m_postponed.emplace_back(scopep, activep);
+                } else {
+                    result.m_comb.emplace_back(scopep, activep);
+                }
+            } else {
+                UASSERT_OBJ(senTreep->hasClocked(), activep, "What else could it be?");
+                result.m_clocked.emplace_back(scopep, activep);
+            }
+        });
+
+        for (AstActive* const activep : empty) activep->unlinkFrBack()->deleteTree();
+    });
+
+    return result;
+}
+
+//============================================================================
+// Simple ordering in source order
+
+void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) {
+    // Create new subfunc for scope
+    const auto createNewSubFuncp = [&](AstScope* const scopep) {
+        const string subName{funcp->name() + "__" + scopep->nameDotless()};
+        AstCFunc* const subFuncp = new AstCFunc{scopep->fileline(), subName, scopep};
+        subFuncp->isLoose(true);
+        subFuncp->isConst(false);
+        subFuncp->declPrivate(true);
+        subFuncp->slow(funcp->slow());
+        scopep->addBlocksp(subFuncp);
+        // Call it from the top function
+        funcp->addStmtsp(new AstCCall{scopep->fileline(), subFuncp});
+        return subFuncp;
+    };
+    const VNUser1InUse user1InUse;  // AstScope -> AstCFunc: the sub-function for the scope
+    const VNUser2InUse user2InUse;  // AstScope -> int: sub-function counter used for names
+    for (const auto& pair : lbs) {
+        AstScope* const scopep = pair.first;
+        AstActive* const activep = pair.second;
+        // Create a sub-function per scope so we can V3Combine them later
+        if (!scopep->user1p()) scopep->user1p(createNewSubFuncp(scopep));
+        // Add statements to sub-function
+        for (AstNode *logicp = activep->stmtsp(), *nextp; logicp; logicp = nextp) {
+            auto* subFuncp = VN_AS(scopep->user1p(), CFunc);
+            nextp = logicp->nextp();
+            if (AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure)) {
+                if (AstNode* bodyp = procp->stmtsp()) {
+                    bodyp->unlinkFrBackWithNext();
+                    // If the process is suspendable, we need a separate function (a coroutine)
+                    if (procp->isSuspendable()) {
+                        subFuncp = createNewSubFuncp(scopep);
+                        subFuncp->name(subFuncp->name() + "__" + cvtToStr(scopep->user2Inc()));
+                        subFuncp->rtnType("VlCoroutine");
+                        if (VN_IS(procp, Always)) {
+                            subFuncp->slow(false);
+                            FileLine* const flp = procp->fileline();
+                            bodyp
+                                = new AstWhile{flp, new AstConst{flp, AstConst::BitTrue{}}, bodyp};
+                        }
+                    }
+                    subFuncp->addStmtsp(bodyp);
+                }
+            } else {
+                logicp->unlinkFrBack();
+                subFuncp->addStmtsp(logicp);
+            }
+        }
+        if (activep->backp()) activep->unlinkFrBack();
+        VL_DO_DANGLING(activep->deleteTree(), activep);
+    }
+}
+
+//============================================================================
+// Create simply ordered functions
+
+void createStatic(AstNetlist* netlistp, const LogicClasses& logicClasses) {
+    AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_static", /* slow: */ true);
+    orderSequentially(funcp, logicClasses.m_static);
+    splitCheck(funcp);
+}
+
+AstCFunc* createInitial(AstNetlist* netlistp, const LogicClasses& logicClasses) {
+    AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_initial", /* slow: */ true);
+    orderSequentially(funcp, logicClasses.m_initial);
+    return funcp;  // Not splitting yet as it is not final
+}
+
+AstCFunc* createPostponed(AstNetlist* netlistp, const LogicClasses& logicClasses) {
+    if (logicClasses.m_postponed.empty()) return nullptr;
+    AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_postponed", /* slow: */ true);
+    orderSequentially(funcp, logicClasses.m_postponed);
+    splitCheck(funcp);
+    return funcp;
+}
+
+void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) {
+    AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_final", /* slow: */ true);
+    orderSequentially(funcp, logicClasses.m_final);
+    splitCheck(funcp);
+}
+
+//============================================================================
+// A TriggerKit holds all the components related to a TRIGGERVEC variable
+
+struct TriggerKit {
+    // The TRIGGERVEC AstVarScope representing these trigger flags
+    AstVarScope* const m_vscp;
+    // The AstCFunc that computes the current active triggers
+    AstCFunc* const m_funcp;
+    // The AstCFunc that dumps the current active triggers
+    AstCFunc* const m_dumpp;
+    // The map from input sensitivity list to trigger sensitivity list
+    const std::unordered_map m_map;
+
+    VL_UNCOPYABLE(TriggerKit);
+
+    // Utility that assigns the given index trigger to fire when the given variable is zero
+    void addFirstIterationTriggerAssignment(AstVarScope* counterp, uint32_t /*index*/) const {
+        FileLine* const flp = counterp->fileline();
+        AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
+        AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, 0}};
+        callp->dtypeSetBit();
+        callp->pure(true);
+        m_funcp->stmtsp()->addHereThisAsNext(new AstAssign{
+            flp, callp,
+            new AstEq{flp, new AstVarRef{flp, counterp, VAccess::READ}, new AstConst{flp, 0}}});
+    }
+
+    // Utility to set then clear the dpiExportTrigger trigger
+    void addDpiExportTriggerAssignment(AstVarScope* dpiExportTriggerVscp, uint32_t index) const {
+        FileLine* const flp = dpiExportTriggerVscp->fileline();
+        AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
+        AstCMethodHard* const callp
+            = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}};
+        callp->dtypeSetBit();
+        callp->pure(true);
+        AstNode* stmtp
+            = new AstAssign{flp, callp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::READ}};
+        stmtp->addNext(new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::WRITE},
+                                     new AstConst{flp, AstConst::BitFalse{}}});
+        m_funcp->stmtsp()->addHereThisAsNext(stmtp);
+    }
+};
+
+// Create an AstSenTree that is sensitive to the given trigger index. Must not exist yet!
+AstSenTree* createTriggerSenTree(AstNetlist* netlistp, AstVarScope* const vscp, uint32_t index) {
+    AstTopScope* const topScopep = netlistp->topScopep();
+    FileLine* const flp = topScopep->fileline();
+    AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::READ};
+    AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}};
+    callp->dtypeSetBit();
+    callp->pure(true);
+    AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, callp};
+    AstSenTree* const resultp = new AstSenTree{flp, senItemp};
+    topScopep->addSenTreesp(resultp);
+    return resultp;
+}
+
+//============================================================================
+// Utility for extra trigger allocation
+
+class ExtraTriggers final {
+    std::vector m_descriptions;  // Human readable descirption of extra triggers
+
+public:
+    ExtraTriggers() = default;
+
+    size_t allocate(const string& description) {
+        const size_t index = m_descriptions.size();
+        m_descriptions.push_back(description);
+        return index;
+    }
+    size_t size() const { return m_descriptions.size(); }
+    const string& description(size_t index) const { return m_descriptions[index]; }
+};
+
+//============================================================================
+// Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector
+
+const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
+                                SenExprBuilder& senExprBuilder,
+                                const std::vector& senTreeps,
+                                const string& name, const ExtraTriggers& extraTriggers,
+                                bool slow = false) {
+    AstTopScope* const topScopep = netlistp->topScopep();
+    AstScope* const scopeTopp = topScopep->scopep();
+    FileLine* const flp = scopeTopp->fileline();
+
+    std::unordered_map map;
+
+    const uint32_t nTriggers = senTreeps.size() + extraTriggers.size();
+
+    // Create the TRIGGERVEC variable
+    AstBasicDType* const tDtypep
+        = new AstBasicDType{flp, VBasicDTypeKwd::TRIGGERVEC, VSigning::UNSIGNED,
+                            static_cast(nTriggers), static_cast(nTriggers)};
+    netlistp->typeTablep()->addTypesp(tDtypep);
+    AstVarScope* const vscp = scopeTopp->createTemp("__V" + name + "Triggered", tDtypep);
+
+    // Create the trigger computation function
+    AstCFunc* const funcp = makeSubFunction(netlistp, "_eval_triggers__" + name, slow);
+
+    // Create the trigger dump function (for debugging, always 'slow')
+    AstCFunc* const dumpp = makeSubFunction(netlistp, "_dump_triggers__" + name, true);
+    dumpp->ifdef("VL_DEBUG");
+
+    // Add a print to the dumping function if there are no triggers pending
+    {
+        AstCMethodHard* const callp
+            = new AstCMethodHard{flp, new AstVarRef{flp, vscp, VAccess::READ}, "any"};
+        callp->dtypeSetBit();
+        AstIf* const ifp = new AstIf{flp, callp};
+        dumpp->addStmtsp(ifp);
+        ifp->addElsesp(
+            new AstText{flp, "VL_DBG_MSGF(\"         No triggers active\\n\");\n", true});
+    }
+
+    // Create a reference to a trigger flag
+    const auto getTrigRef = [&](uint32_t index, VAccess access) {
+        AstVarRef* const vrefp = new AstVarRef{flp, vscp, access};
+        AstConst* const idxp = new AstConst{flp, index};
+        AstCMethodHard* callp = new AstCMethodHard{flp, vrefp, "at", idxp};
+        callp->dtypeSetBit();
+        callp->pure(true);
+        return callp;
+    };
+
+    // Add a debug dumping statement for this trigger
+    const auto addDebug = [&](uint32_t index, const string& text = "") {
+        std::stringstream ss;
+        ss << "VL_DBG_MSGF(\"         '" << name << "' region trigger index " << cvtToStr(index)
+           << " is active";
+        if (!text.empty()) ss << ": " << text;
+        ss << "\\n\");\n";
+        const string message{ss.str()};
+
+        AstIf* const ifp = new AstIf{flp, getTrigRef(index, VAccess::READ)};
+        dumpp->addStmtsp(ifp);
+        ifp->addThensp(new AstText{flp, message, true});
+    };
+
+    // Add a print for each of the extra triggers
+    for (unsigned i = 0; i < extraTriggers.size(); ++i) {
+        addDebug(i, "Internal '" + name + "' trigger - " + extraTriggers.description(i));
+    }
+
+    // Add trigger computation
+    uint32_t triggerNumber = extraTriggers.size();
+    AstNode* initialTrigsp = nullptr;
+    for (const AstSenTree* const senTreep : senTreeps) {
+        UASSERT_OBJ(senTreep->hasClocked() || senTreep->hasHybrid(), senTreep,
+                    "Cannot create trigger expression for non-clocked sensitivity");
+
+        // Create the trigger AstSenTrees and associate it with the original AstSenTree
+        AstCMethodHard* const senp = getTrigRef(triggerNumber, VAccess::READ);
+        AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, senp};
+        AstSenTree* const trigpSenp = new AstSenTree{flp, senItemp};
+        topScopep->addSenTreesp(trigpSenp);
+        map[senTreep] = trigpSenp;
+
+        // Add the trigger computation
+        const auto& pair = senExprBuilder.build(senTreep);
+        funcp->addStmtsp(
+            new AstAssign{flp, getTrigRef(triggerNumber, VAccess::WRITE), pair.first});
+
+        // Add initialization time trigger
+        if (pair.second || v3Global.opt.xInitialEdge()) {
+            AstNode* const assignp = new AstAssign{flp, getTrigRef(triggerNumber, VAccess::WRITE),
+                                                   new AstConst{flp, 1}};
+            initialTrigsp = AstNode::addNext(initialTrigsp, assignp);
+        }
+
+        // Add a debug statement for this trigger
+        std::stringstream ss;
+        V3EmitV::verilogForTree(senTreep, ss);
+        addDebug(triggerNumber, ss.str());
+
+        //
+        ++triggerNumber;
+    }
+    // Add the init and update statements
+    for (AstNodeStmt* const nodep : senExprBuilder.getAndClearInits()) {
+        initFuncp->addStmtsp(nodep);
+    }
+    for (AstNodeStmt* const nodep : senExprBuilder.getAndClearPostUpdates()) {
+        funcp->addStmtsp(nodep);
+    }
+    const auto& preUpdates = senExprBuilder.getAndClearPreUpdates();
+    if (!preUpdates.empty()) {
+        for (AstNodeStmt* const nodep : vlstd::reverse_view(preUpdates)) {
+            UASSERT_OBJ(funcp->stmtsp(), funcp,
+                        "No statements in trigger eval function, but there are pre updates");
+            funcp->stmtsp()->addHereThisAsNext(nodep);
+        }
+    }
+    const auto& locals = senExprBuilder.getAndClearLocals();
+    if (!locals.empty()) {
+        UASSERT_OBJ(funcp->stmtsp(), funcp,
+                    "No statements in trigger eval function, but there are locals");
+        for (AstVar* const nodep : vlstd::reverse_view(locals)) {
+            funcp->stmtsp()->addHereThisAsNext(nodep);
+        }
+    }
+
+    // Add the initialization statements
+    if (initialTrigsp) {
+        AstVarScope* const vscp = scopeTopp->createTemp("__V" + name + "DidInit", 1);
+        AstVarRef* const condp = new AstVarRef{flp, vscp, VAccess::READ};
+        AstIf* const ifp = new AstIf{flp, new AstNot{flp, condp}};
+        funcp->addStmtsp(ifp);
+        ifp->branchPred(VBranchPred::BP_UNLIKELY);
+        ifp->addThensp(setVar(vscp, 1));
+        ifp->addThensp(initialTrigsp);
+    }
+
+    // Add a call to the dumping function if debug is enabled
+    {
+        AstTextBlock* const blockp = new AstTextBlock{flp};
+        funcp->addStmtsp(blockp);
+        const auto add = [&](const string& text) { blockp->addText(flp, text, true); };
+        add("#ifdef VL_DEBUG\n");
+        add("if (VL_UNLIKELY(vlSymsp->_vm_contextp__->debug())) {\n");
+        blockp->addNodesp(new AstCCall{flp, dumpp});
+        add("}\n");
+        add("#endif\n");
+    }
+
+    // The debug code might leak signal names, so simply delete it when using --protect-ids
+    if (v3Global.opt.protectIds()) dumpp->stmtsp()->unlinkFrBackWithNext()->deleteTree();
+
+    return {vscp, funcp, dumpp, map};
+}
+
+//============================================================================
+// Helpers to construct an evaluation loop.
+
+AstNode* buildLoop(AstNetlist* netlistp, const string& name,
+                   const std::function& build)  //
+{
+    AstTopScope* const topScopep = netlistp->topScopep();
+    AstScope* const scopeTopp = topScopep->scopep();
+    FileLine* const flp = scopeTopp->fileline();
+    // Create the loop condition variable
+    AstVarScope* const condp = scopeTopp->createTemp("__V" + name + "Continue", 1);
+    // Initialize the loop condition variable to true
+    AstNode* const resp = setVar(condp, 1);
+    // Add the loop
+    AstWhile* const loopp = new AstWhile{flp, new AstVarRef{flp, condp, VAccess::READ}};
+    resp->addNext(loopp);
+    // Clear the loop condition variable in the loop
+    loopp->addStmtsp(setVar(condp, 0));
+    // Build the body
+    build(condp, loopp);
+    // Done
+    return resp;
+};
+
+std::pair makeEvalLoop(AstNetlist* netlistp, const string& tag,
+                                               const string& name, AstVarScope* trigVscp,
+                                               AstCFunc* trigDumpp,
+                                               std::function computeTriggers,
+                                               std::function makeBody) {
+    UASSERT_OBJ(trigVscp->dtypep()->basicp()->isTriggerVec(), trigVscp, "Not TRIGGERVEC");
+    AstTopScope* const topScopep = netlistp->topScopep();
+    AstScope* const scopeTopp = topScopep->scopep();
+    FileLine* const flp = scopeTopp->fileline();
+
+    AstVarScope* const counterp = scopeTopp->createTemp("__V" + tag + "IterCount", 32);
+
+    AstNode* nodep = setVar(counterp, 0);
+    nodep->addNext(buildLoop(netlistp, tag, [&](AstVarScope* continuep, AstWhile* loopp) {
+        // Compute triggers
+        loopp->addStmtsp(computeTriggers());
+        // Invoke body if triggered
+        {
+            AstVarRef* const refp = new AstVarRef{flp, trigVscp, VAccess::READ};
+            AstCMethodHard* const callp = new AstCMethodHard{flp, refp, "any"};
+            callp->dtypeSetBit();
+            AstIf* const ifp = new AstIf{flp, callp};
+            loopp->addStmtsp(ifp);
+            ifp->addThensp(setVar(continuep, 1));
+
+            // If we exceeded the iteration limit, die
+            {
+                const uint32_t limit = v3Global.opt.convergeLimit();
+                AstVarRef* const refp = new AstVarRef{flp, counterp, VAccess::READ};
+                AstConst* const constp = new AstConst{flp, AstConst::DTyped{}, counterp->dtypep()};
+                constp->num().setLong(limit);
+                AstNodeMath* const condp = new AstGt{flp, refp, constp};
+                AstIf* const failp = new AstIf{flp, condp};
+                ifp->addThensp(failp);
+                AstTextBlock* const blockp = new AstTextBlock{flp};
+                failp->addThensp(blockp);
+                FileLine* const locp = netlistp->topModulep()->fileline();
+                const string& file = EmitCBaseVisitor::protect(locp->filename());
+                const string& line = cvtToStr(locp->lineno());
+                const auto add = [&](const string& text) { blockp->addText(flp, text, true); };
+                add("#ifdef VL_DEBUG\n");
+                blockp->addNodesp(new AstCCall{flp, trigDumpp});
+                add("#endif\n");
+                add("VL_FATAL_MT(\"" + file + "\", " + line + ", \"\", ");
+                add("\"" + name + " region did not converge.\");\n");
+            }
+
+            // Increment iteration count
+            {
+                AstVarRef* const wrefp = new AstVarRef{flp, counterp, VAccess::WRITE};
+                AstVarRef* const rrefp = new AstVarRef{flp, counterp, VAccess::READ};
+                AstConst* const onep = new AstConst{flp, AstConst::DTyped{}, counterp->dtypep()};
+                onep->num().setLong(1);
+                ifp->addThensp(new AstAssign{flp, wrefp, new AstAdd{flp, rrefp, onep}});
+            }
+
+            // Add body
+            ifp->addThensp(makeBody());
+        }
+    }));
+
+    return {counterp, nodep};
+}
+
+//============================================================================
+// Order the combinational logic to create the settle loop
+
+void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilder& senExprBulider,
+                  LogicClasses& logicClasses) {
+    AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_settle", true);
+
+    // Clone, because ordering is destructive, but we still need them for "_eval"
+    LogicByScope comb = logicClasses.m_comb.clone();
+    LogicByScope hybrid = logicClasses.m_hybrid.clone();
+
+    // Nothing to do if there is no logic.
+    // While this is rare in real designs, it reduces noise in small tests.
+    if (comb.empty() && hybrid.empty()) return;
+
+    // We have an extra trigger denoting this is the first iteration of the settle loop
+    ExtraTriggers extraTriggers;
+    const size_t firstIterationTrigger = extraTriggers.allocate("first iteration");
+
+    // Gather the relevant sensitivity expressions and create the trigger kit
+    const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid});
+    const TriggerKit& trig = createTriggers(netlistp, initFuncp, senExprBulider, senTreeps, "stl",
+                                            extraTriggers, true);
+
+    // Remap sensitivities (comb has none, so only do the hybrid)
+    remapSensitivities(hybrid, trig.m_map);
+
+    // Create the inverse map from trigger ref AstSenTree to original AstSenTree
+    std::unordered_map trigToSen;
+    invertAndMergeSenTreeMap(trigToSen, trig.m_map);
+
+    // First trigger is for pure combinational triggers (first iteration)
+    AstSenTree* const inputChanged
+        = createTriggerSenTree(netlistp, trig.m_vscp, firstIterationTrigger);
+
+    // Create and the body function
+    AstCFunc* const stlFuncp = V3Order::order(
+        netlistp, {&comb, &hybrid}, trigToSen, "stl", false, true,
+        [=](const AstVarScope*, std::vector& out) { out.push_back(inputChanged); });
+    splitCheck(stlFuncp);
+
+    // Create the eval loop
+    const auto& pair = makeEvalLoop(
+        netlistp, "stl", "Settle", trig.m_vscp, trig.m_dumpp,
+        [&]() {  // Trigger
+            return new AstCCall{stlFuncp->fileline(), trig.m_funcp};
+        },
+        [&]() {  // Body
+            return new AstCCall{stlFuncp->fileline(), stlFuncp};
+        });
+
+    // Add the first iteration trigger to the trigger computation function
+    trig.addFirstIterationTriggerAssignment(pair.first, firstIterationTrigger);
+
+    // Add the eval loop to the top function
+    funcp->addStmtsp(pair.second);
+}
+
+//============================================================================
+// Order the replicated combinational logic to create the 'ico' region
+
+AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
+                             SenExprBuilder& senExprBuilder, LogicByScope& logic) {
+    // Nothing to do if no combinational logic is sensitive to top level inputs
+    if (logic.empty()) return nullptr;
+
+    // SystemC only: Any top level inputs feeding a combinational logic must be marked,
+    // so we can make them sc_sensitive
+    if (v3Global.opt.systemC()) {
+        logic.foreachLogic([](AstNode* logicp) {
+            logicp->foreach([](AstVarRef* refp) {
+                if (refp->access().isWriteOnly()) return;
+                AstVarScope* const vscp = refp->varScopep();
+                if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) {
+                    vscp->varp()->scSensitive(true);
+                }
+            });
+        });
+    }
+
+    // We have some extra trigger denoting external conditions
+    AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
+
+    ExtraTriggers extraTriggers;
+    const size_t firstIterationTrigger = extraTriggers.allocate("first iteration");
+    const size_t dpiExportTriggerIndex = dpiExportTriggerVscp
+                                             ? extraTriggers.allocate("DPI export trigger")
+                                             : std::numeric_limits::max();
+
+    // Gather the relevant sensitivity expressions and create the trigger kit
+    const auto& senTreeps = getSenTreesUsedBy({&logic});
+    const TriggerKit& trig
+        = createTriggers(netlistp, initFuncp, senExprBuilder, senTreeps, "ico", extraTriggers);
+
+    if (dpiExportTriggerVscp) {
+        trig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
+    }
+
+    // Remap sensitivities
+    remapSensitivities(logic, trig.m_map);
+
+    // Create the inverse map from trigger ref AstSenTree to original AstSenTree
+    std::unordered_map trigToSen;
+    invertAndMergeSenTreeMap(trigToSen, trig.m_map);
+
+    // The trigger top level inputs (first iteration)
+    AstSenTree* const inputChanged
+        = createTriggerSenTree(netlistp, trig.m_vscp, firstIterationTrigger);
+
+    // The DPI Export trigger
+    AstSenTree* const dpiExportTriggered
+        = createTriggerSenTree(netlistp, trig.m_vscp, dpiExportTriggerIndex);
+
+    // Create and Order the body function
+    AstCFunc* const icoFuncp
+        = V3Order::order(netlistp, {&logic}, trigToSen, "ico", false, false,
+                         [=](const AstVarScope* vscp, std::vector& out) {
+                             AstVar* const varp = vscp->varp();
+                             if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) {
+                                 out.push_back(inputChanged);
+                             }
+                             if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered);
+                         });
+    splitCheck(icoFuncp);
+
+    // Create the eval loop
+    const auto& pair = makeEvalLoop(
+        netlistp, "ico", "Input combinational", trig.m_vscp, trig.m_dumpp,
+        [&]() {  // Trigger
+            return new AstCCall{icoFuncp->fileline(), trig.m_funcp};
+        },
+        [&]() {  // Body
+            return new AstCCall{icoFuncp->fileline(), icoFuncp};
+        });
+
+    // Add the first iteration trigger to the trigger computation function
+    trig.addFirstIterationTriggerAssignment(pair.first, firstIterationTrigger);
+
+    // Return the eval loop itself
+    return pair.second;
+}
+
+//============================================================================
+// Bold together parts to create the top level _eval function
+
+void createEval(AstNetlist* netlistp,  //
+                AstNode* icoLoop,  //
+                const TriggerKit& actTrig,  //
+                AstVarScope* preTrigsp,  //
+                AstVarScope* nbaTrigsp,  //
+                AstCFunc* actFuncp,  //
+                AstCFunc* nbaFuncp,  //
+                AstCFunc* postponedFuncp,  //
+                TimingKit& timingKit  //
+) {
+    FileLine* const flp = netlistp->fileline();
+
+    AstCFunc* const funcp = makeTopFunction(netlistp, "_eval", false);
+    netlistp->evalp(funcp);
+
+    // Start with the ico loop, if any
+    if (icoLoop) funcp->addStmtsp(icoLoop);
+
+    // Create the NBA trigger dumping function, which is the same as act trigger
+    // dumping function, but referencing the nba trigger vector.
+    AstCFunc* const nbaDumpp = actTrig.m_dumpp->cloneTree(false);
+    actTrig.m_dumpp->addNextHere(nbaDumpp);
+    nbaDumpp->name("_dump_triggers__nba");
+    nbaDumpp->foreach([&](AstVarRef* refp) {
+        UASSERT_OBJ(refp->access().isReadOnly(), refp, "Should only read state");
+        if (refp->varScopep() == actTrig.m_vscp) {
+            refp->replaceWith(new AstVarRef{refp->fileline(), nbaTrigsp, VAccess::READ});
+        }
+    });
+    nbaDumpp->foreach([&](AstText* textp) {  //
+        textp->text(VString::replaceWord(textp->text(), "act", "nba"));
+    });
+
+    // Create the active eval loop
+    AstNode* const activeEvalLoopp
+        = makeEvalLoop(
+              netlistp, "act", "Active", actTrig.m_vscp, actTrig.m_dumpp,
+              [&]() {  // Trigger
+                  AstNode* const resultp = new AstCCall{flp, actTrig.m_funcp};
+                  // Commit trigger awaits from the previous iteration
+                  if (AstNode* const commitp = timingKit.createCommit(netlistp)) {
+                      resultp->addNext(commitp);
+                  }
+                  return resultp;
+              },
+              [&]() {  // Body
+                  AstNode* resultp = nullptr;
+
+                  // Compute the pre triggers
+                  {
+                      AstVarRef* const lhsp = new AstVarRef{flp, preTrigsp, VAccess::WRITE};
+                      AstVarRef* const opap = new AstVarRef{flp, actTrig.m_vscp, VAccess::READ};
+                      AstVarRef* const opbp = new AstVarRef{flp, nbaTrigsp, VAccess::READ};
+                      opap->addNext(opbp);
+                      AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "andNot", opap};
+                      callp->statement(true);
+                      callp->dtypeSetVoid();
+                      resultp = AstNode::addNext(resultp, callp);
+                  }
+
+                  // Latch the active trigger flags under the NBA trigger flags
+                  {
+                      AstVarRef* const lhsp = new AstVarRef{flp, nbaTrigsp, VAccess::WRITE};
+                      AstVarRef* const argp = new AstVarRef{flp, actTrig.m_vscp, VAccess::READ};
+                      AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "set", argp};
+                      callp->statement(true);
+                      callp->dtypeSetVoid();
+                      resultp = AstNode::addNext(resultp, callp);
+                  }
+
+                  // Resume triggered timing schedulers
+                  if (AstNode* const resumep = timingKit.createResume(netlistp)) {
+                      resultp = AstNode::addNext(resultp, resumep);
+                  }
+
+                  // Invoke body function
+                  return AstNode::addNext(resultp, new AstCCall{flp, actFuncp});
+              })
+              .second;
+
+    // Create the NBA eval loop. This uses the Active eval loop in the trigger section.
+    AstNode* const nbaEvalLoopp
+        = makeEvalLoop(
+              netlistp, "nba", "NBA", nbaTrigsp, nbaDumpp,
+              [&]() {  // Trigger
+                  AstNode* resultp = nullptr;
+
+                  // Reset NBA triggers
+                  {
+                      AstVarRef* const refp = new AstVarRef{flp, nbaTrigsp, VAccess::WRITE};
+                      AstCMethodHard* const callp = new AstCMethodHard{flp, refp, "clear"};
+                      callp->statement(true);
+                      callp->dtypeSetVoid();
+                      resultp = AstNode::addNext(resultp, callp);
+                  }
+
+                  // Run the Active eval loop
+                  return AstNode::addNext(resultp, activeEvalLoopp);
+              },
+              [&]() {  // Body
+                  return new AstCCall{flp, nbaFuncp};
+              })
+              .second;
+
+    // Add the NBA eval loop
+    funcp->addStmtsp(nbaEvalLoopp);
+
+    // Add the Postponed eval call
+    if (postponedFuncp) funcp->addStmtsp(new AstCCall{flp, postponedFuncp});
+}
+
+}  // namespace
+
+//============================================================================
+// Top level entry-point to scheduling
+
+void schedule(AstNetlist* netlistp) {
+    const auto addSizeStat = [](const string& name, const LogicByScope& lbs) {
+        uint64_t size = 0;
+        lbs.foreachLogic([&](AstNode* nodep) { size += nodep->nodeCount(); });
+        V3Stats::addStat("Scheduling, " + name, size);
+    };
+
+    // Step 0. Prepare timing-related logic and external domains
+    auto timingKit = prepareTiming(netlistp);
+
+    // Step 1. Gather and classify all logic in the design
+    LogicClasses logicClasses = gatherLogicClasses(netlistp);
+
+    if (v3Global.opt.stats()) {
+        V3Stats::statsStage("sched-gather");
+        addSizeStat("size of class: static", logicClasses.m_static);
+        addSizeStat("size of class: initial", logicClasses.m_initial);
+        addSizeStat("size of class: final", logicClasses.m_final);
+    }
+
+    // Step 2. Schedule static, initial and final logic classes in source order
+    createStatic(netlistp, logicClasses);
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-static");
+
+    AstCFunc* const initp = createInitial(netlistp, logicClasses);
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-initial");
+
+    createFinal(netlistp, logicClasses);
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-final");
+
+    // Step 3: Break combinational cycles by introducing hybrid logic
+    // Note: breakCycles also removes corresponding logic from logicClasses.m_comb;
+    logicClasses.m_hybrid = breakCycles(netlistp, logicClasses.m_comb);
+    if (v3Global.opt.stats()) {
+        addSizeStat("size of class: clocked", logicClasses.m_clocked);
+        addSizeStat("size of class: combinational", logicClasses.m_comb);
+        addSizeStat("size of class: hybrid", logicClasses.m_hybrid);
+        V3Stats::statsStage("sched-break-cycles");
+    }
+
+    // We pass around a single SenExprBuilder instance, as we only need one set of 'prev' variables
+    // for edge/change detection in sensitivity expressions, which this keeps track of.
+    AstTopScope* const topScopep = netlistp->topScopep();
+    AstScope* const scopeTopp = topScopep->scopep();
+    SenExprBuilder senExprBuilder{scopeTopp};
+
+    // Step 4: Create 'settle' region that restores the combinational invariant
+    createSettle(netlistp, initp, senExprBuilder, logicClasses);
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-settle");
+
+    // Step 5: Partition the clocked and combinational (including hybrid) logic into pre/act/nba.
+    // All clocks (signals referenced in an AstSenTree) generated via a blocking assignment
+    // (including combinationally generated signals) are computed within the act region.
+    LogicRegions logicRegions
+        = partition(logicClasses.m_clocked, logicClasses.m_comb, logicClasses.m_hybrid);
+    if (v3Global.opt.stats()) {
+        addSizeStat("size of region: Active Pre", logicRegions.m_pre);
+        addSizeStat("size of region: Active", logicRegions.m_act);
+        addSizeStat("size of region: NBA", logicRegions.m_nba);
+        V3Stats::statsStage("sched-partition");
+    }
+
+    // Step 6: Replicate combinational logic
+    LogicReplicas logicReplicas = replicateLogic(logicRegions);
+    if (v3Global.opt.stats()) {
+        addSizeStat("size of replicated logic: Input", logicReplicas.m_ico);
+        addSizeStat("size of replicated logic: Active", logicReplicas.m_act);
+        addSizeStat("size of replicated logic: NBA", logicReplicas.m_nba);
+        V3Stats::statsStage("sched-replicate");
+    }
+
+    // Step 7: Create input combinational logic loop
+    AstNode* const icoLoopp
+        = createInputCombLoop(netlistp, initp, senExprBuilder, logicReplicas.m_ico);
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico");
+
+    // Step 8: Create the pre/act/nba triggers
+    AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
+
+    // We may have an extra trigger for variable updated in DPI exports
+    ExtraTriggers extraTriggers;
+    const size_t dpiExportTriggerIndex = dpiExportTriggerVscp
+                                             ? extraTriggers.allocate("DPI export trigger")
+                                             : std::numeric_limits::max();
+
+    const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre,  //
+                                               &logicRegions.m_act,  //
+                                               &logicRegions.m_nba,  //
+                                               &timingKit.m_lbs});
+    const TriggerKit& actTrig
+        = createTriggers(netlistp, initp, senExprBuilder, senTreeps, "act", extraTriggers);
+
+    // Add post updates from the timing kit
+    if (timingKit.m_postUpdates) actTrig.m_funcp->addStmtsp(timingKit.m_postUpdates);
+
+    if (dpiExportTriggerVscp) {
+        actTrig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
+    }
+
+    AstVarScope* const actTrigVscp = actTrig.m_vscp;
+    AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp);
+    AstVarScope* const nbaTrigVscp = scopeTopp->createTempLike("__VnbaTriggered", actTrigVscp);
+
+    const auto cloneMapWithNewTriggerReferences
+        = [=](std::unordered_map map, AstVarScope* vscp) {
+              // Copy map
+              auto newMap{map};
+              VNDeleter deleter;
+              // Replace references in each mapped value with a reference to the given vscp
+              for (auto& pair : newMap) {
+                  pair.second = pair.second->cloneTree(false);
+                  pair.second->foreach([&](AstVarRef* refp) {
+                      UASSERT_OBJ(refp->varScopep() == actTrigVscp, refp, "Unexpected reference");
+                      UASSERT_OBJ(refp->access() == VAccess::READ, refp, "Should be read ref");
+                      refp->replaceWith(new AstVarRef{refp->fileline(), vscp, VAccess::READ});
+                      deleter.pushDeletep(refp);
+                  });
+                  topScopep->addSenTreesp(pair.second);
+              }
+              return newMap;
+          };
+
+    const auto& actTrigMap = actTrig.m_map;
+    const auto preTrigMap = cloneMapWithNewTriggerReferences(actTrigMap, preTrigVscp);
+    const auto nbaTrigMap = cloneMapWithNewTriggerReferences(actTrigMap, nbaTrigVscp);
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-triggers");
+
+    // Note: Experiments so far show that running the Act (or Ico) regions on
+    // multiple threads is always a net loss, so only use multi-threading for
+    // NBA for now. This can be revised if evidence is available that it would
+    // be beneficial
+
+    // Step 9: Create the 'act' region evaluation function
+
+    // Remap sensitivities of the input logic to the triggers
+    remapSensitivities(logicRegions.m_pre, preTrigMap);
+    remapSensitivities(logicRegions.m_act, actTrigMap);
+    remapSensitivities(logicReplicas.m_act, actTrigMap);
+    remapSensitivities(timingKit.m_lbs, actTrigMap);
+    const auto& actTimingDomains = timingKit.remapDomains(actTrigMap);
+
+    // Create the inverse map from trigger ref AstSenTree to original AstSenTree
+    std::unordered_map trigToSenAct;
+    invertAndMergeSenTreeMap(trigToSenAct, preTrigMap);
+    invertAndMergeSenTreeMap(trigToSenAct, actTrigMap);
+
+    // The DPI Export trigger AstSenTree
+    AstSenTree* const dpiExportTriggeredAct
+        = createTriggerSenTree(netlistp, actTrig.m_vscp, dpiExportTriggerIndex);
+
+    AstCFunc* const actFuncp = V3Order::order(
+        netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct,
+        "act", false, false, [&](const AstVarScope* vscp, std::vector& out) {
+            auto it = actTimingDomains.find(vscp);
+            if (it != actTimingDomains.end()) out = it->second;
+            if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct);
+        });
+    splitCheck(actFuncp);
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act");
+
+    // Step 10: Create the 'nba' region evaluation function
+
+    // Remap sensitivities of the input logic to the triggers
+    remapSensitivities(logicRegions.m_nba, nbaTrigMap);
+    remapSensitivities(logicReplicas.m_nba, nbaTrigMap);
+    const auto& nbaTimingDomains = timingKit.remapDomains(nbaTrigMap);
+
+    // Create the inverse map from trigger ref AstSenTree to original AstSenTree
+    std::unordered_map trigToSenNba;
+    invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap);
+
+    AstSenTree* const dpiExportTriggeredNba
+        = createTriggerSenTree(netlistp, nbaTrigVscp, dpiExportTriggerIndex);
+
+    AstCFunc* const nbaFuncp = V3Order::order(
+        netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba",
+        v3Global.opt.mtasks(), false, [&](const AstVarScope* vscp, std::vector& out) {
+            auto it = nbaTimingDomains.find(vscp);
+            if (it != nbaTimingDomains.end()) out = it->second;
+            if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredNba);
+        });
+    splitCheck(nbaFuncp);
+    netlistp->evalNbap(nbaFuncp);  // Remember for V3LifePost
+    if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba");
+
+    // Step 11: Create the 'postponed' region evaluation function
+    auto* const postponedFuncp = createPostponed(netlistp, logicClasses);
+
+    // Step 12: Bolt it all together to create the '_eval' function
+    createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp,
+               postponedFuncp, timingKit);
+
+    transformForks(netlistp);
+
+    splitCheck(initp);
+
+    netlistp->dpiExportTriggerp(nullptr);
+
+    V3Global::dumpCheckGlobalTree("sched", 0, dumpTree() >= 3);
+}
+
+}  // namespace V3Sched
diff --git a/src/V3Sched.h b/src/V3Sched.h
new file mode 100644
index 000000000..efde069af
--- /dev/null
+++ b/src/V3Sched.h
@@ -0,0 +1,166 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Scheduling
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3SCHED_H_
+#define VERILATOR_V3SCHED_H_
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+
+#include 
+#include 
+#include 
+#include 
+
+//============================================================================
+
+namespace V3Sched {
+
+//============================================================================
+// Throughout scheduling, we need to keep hold of AstActive nodes, together with the AstScope that
+// they are under. LogicByScope is simply a vector of such pairs, with some additional convenience
+// methods.
+struct LogicByScope final : public std::vector> {
+    // Add logic
+    void add(AstScope* scopep, AstSenTree* senTreep, AstNode* logicp) {
+        UASSERT_OBJ(!logicp->backp(), logicp, "Already linked");
+        if (empty() || back().first != scopep || back().second->sensesp() != senTreep) {
+            emplace_back(scopep, new AstActive{logicp->fileline(), "", senTreep});
+        }
+        back().second->addStmtsp(logicp);
+    };
+
+    // Create copy, with the AstActives cloned
+    LogicByScope clone() const {
+        LogicByScope result;
+        for (const auto& pair : *this) {
+            result.emplace_back(pair.first, pair.second->cloneTree(false));
+        }
+        return result;
+    }
+
+    // Delete actives (they should all be empty)
+    void deleteActives() {
+        for (const auto& pair : *this) {
+            AstActive* const activep = pair.second;
+            UASSERT_OBJ(!activep->stmtsp(), activep, "Leftover logic");
+            if (activep->backp()) activep->unlinkFrBack();
+            activep->deleteTree();
+        }
+        clear();
+    };
+
+    void foreachLogic(const std::function& f) const {
+        for (const auto& pair : *this) {
+            for (AstNode* nodep = pair.second->stmtsp(); nodep; nodep = nodep->nextp()) f(nodep);
+        }
+    }
+};
+
+// Logic in the design is classified based on what can trigger its execution.
+// For details see the internals documentation.
+struct LogicClasses final {
+    LogicByScope m_static;  // Static variable initializers
+    LogicByScope m_initial;  // initial blocks
+    LogicByScope m_final;  // final blocks
+    LogicByScope m_comb;  // Combinational logic (logic with implicit sensitivities)
+    LogicByScope m_clocked;  // Clocked (or sequential) logic (logic with explictit sensitivities)
+    LogicByScope m_hybrid;  // Hybrid logic (combinational logic with some explicit sensitivities)
+    LogicByScope m_postponed;  // Postponed logic ($strobe)
+
+    LogicClasses() = default;
+    VL_UNCOPYABLE(LogicClasses);
+    LogicClasses(LogicClasses&&) = default;
+    LogicClasses& operator=(LogicClasses&&) = default;
+};
+
+// Combinational (including hybrid) logic, and clocked logic in partitioned to compute all clock
+// signals in the 'act' region. For details see the internals documentation.
+struct LogicRegions final {
+    LogicByScope m_pre;  // AstAssignPre logic in 'act' region
+    LogicByScope m_act;  // 'act' region logic
+    LogicByScope m_nba;  // 'nba' region logic
+
+    LogicRegions() = default;
+    VL_UNCOPYABLE(LogicRegions);
+    LogicRegions(LogicRegions&&) = default;
+    LogicRegions& operator=(LogicRegions&&) = default;
+};
+
+// Combinational (including hybrid) logic is replicated into the various scheduling regions.
+// For details see the internals documentation.
+struct LogicReplicas final {
+    LogicByScope m_ico;  // Logic replicated into the 'ico' (Input Combinational) region
+    LogicByScope m_act;  // Logic replicated into the 'act' region
+    LogicByScope m_nba;  // Logic replicated into the 'nba' region
+
+    LogicReplicas() = default;
+    VL_UNCOPYABLE(LogicReplicas);
+    LogicReplicas(LogicReplicas&&) = default;
+    LogicReplicas& operator=(LogicReplicas&&) = default;
+};
+
+// Everything needed for combining timing with static scheduling.
+class TimingKit final {
+    AstCFunc* m_resumeFuncp = nullptr;  // Global timing resume function
+    AstCFunc* m_commitFuncp = nullptr;  // Global timing commit function
+
+    // Additional var sensitivities for V3Order
+    std::map> m_externalDomains;
+
+public:
+    LogicByScope m_lbs;  // Actives that resume timing schedulers
+    AstNodeStmt* m_postUpdates = nullptr;  // Post updates for the trigger eval function
+
+    // Remaps external domains using the specified trigger map
+    std::map>
+    remapDomains(const std::unordered_map& trigMap) const;
+    // Creates a timing resume call (if needed, else returns null)
+    AstCCall* createResume(AstNetlist* const netlistp);
+    // Creates a timing commit call (if needed, else returns null)
+    AstCCall* createCommit(AstNetlist* const netlistp);
+
+    TimingKit() = default;
+    TimingKit(LogicByScope&& lbs, AstNodeStmt* postUpdates,
+              std::map>&& externalDomains)
+        : m_externalDomains{externalDomains}
+        , m_lbs{lbs}
+        , m_postUpdates{postUpdates} {}
+    VL_UNCOPYABLE(TimingKit);
+    TimingKit(TimingKit&&) = default;
+    TimingKit& operator=(TimingKit&&) = default;
+};
+
+// Creates the timing kit and marks variables written by suspendables
+TimingKit prepareTiming(AstNetlist* const netlistp);
+
+// Transforms fork sub-statements into separate functions
+void transformForks(AstNetlist* const netlistp);
+
+// Top level entry point to scheduling
+void schedule(AstNetlist*);
+
+// Sub-steps
+LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic);
+LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
+                       LogicByScope& hybridLogic);
+LogicReplicas replicateLogic(LogicRegions&);
+
+}  // namespace V3Sched
+
+#endif  // Guard
diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp
new file mode 100644
index 000000000..89c783fb8
--- /dev/null
+++ b/src/V3SchedAcyclic.cpp
@@ -0,0 +1,422 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Scheduling - break combinational cycles
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// Combinational loops are broken by the introduction of instances of the
+// 'hybrid' logic. Hybrid logic is like combinational logic, but also has
+// explicit sensitivities. Any explicit sensitivity of hybrid logic suppresses
+// the implicit sensitivity of the logic on the same variable. This enables us
+// to cut combinational logic loops and perform ordering as if the logic is
+// acyclic.  See the internals documentation for more details.
+//
+// To achieve this we build a dependency graph of all combinational logic in
+// the design, and then breaks all combinational cycles by converting all
+// combinational logic that consumes a variable driven via a 'back-edge' into
+// hybrid logic. Here back-edge' just means a graph edge that points from a
+// higher rank vertex to a lower rank vertex in some consistent ranking of
+// the directed graph. Variables driven via a back-edge in the dependency
+// graph are marked, and all combinational logic that depends on such
+// variables is converted into hybrid logic, with the back-edge driven
+// variables listed as explicit 'changed' sensitivities.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+#include "V3Error.h"
+#include "V3Global.h"
+#include "V3Graph.h"
+#include "V3Sched.h"
+#include "V3SenTree.h"
+#include "V3SplitVar.h"
+#include "V3Stats.h"
+
+#include 
+#include 
+#include 
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+namespace V3Sched {
+
+namespace {
+
+// ##############################################################################
+//  Data structures (graph types)
+
+class LogicVertex final : public V3GraphVertex {
+    AstNode* const m_logicp;  // The logic node this vertex represents
+    AstScope* const m_scopep;  // The enclosing AstScope of the logic node
+
+public:
+    LogicVertex(V3Graph* graphp, AstNode* logicp, AstScope* scopep)
+        : V3GraphVertex{graphp}
+        , m_logicp{logicp}
+        , m_scopep{scopep} {}
+    AstNode* logicp() const { return m_logicp; }
+    AstScope* scopep() const { return m_scopep; }
+
+    // LCOV_EXCL_START // Debug code
+    string name() const override { return m_logicp->fileline()->ascii(); };
+    string dotShape() const override { return "rectangle"; }
+    // LCOV_EXCL_STOP
+};
+
+class VarVertex final : public V3GraphVertex {
+    AstVarScope* const m_vscp;  // The AstVarScope this vertex represents
+
+public:
+    VarVertex(V3Graph* graphp, AstVarScope* vscp)
+        : V3GraphVertex{graphp}
+        , m_vscp{vscp} {}
+    AstVarScope* vscp() const { return m_vscp; }
+    AstVar* varp() const { return m_vscp->varp(); }
+
+    // LCOV_EXCL_START // Debug code
+    string name() const override { return m_vscp->name(); }
+    string dotShape() const override { return "ellipse"; }
+    string dotColor() const override { return "blue"; }
+    // LCOV_EXCL_STOP
+};
+
+class Graph final : public V3Graph {
+    void loopsVertexCb(V3GraphVertex* vtxp) override {
+        // TODO: 'typeName' is an internal thing. This should be more human readable.
+        if (LogicVertex* const lvtxp = dynamic_cast(vtxp)) {
+            AstNode* const logicp = lvtxp->logicp();
+            std::cerr << logicp->fileline()->warnOther()
+                      << "     Example path: " << logicp->typeName() << endl;
+        } else {
+            VarVertex* const vvtxp = dynamic_cast(vtxp);
+            UASSERT(vvtxp, "Cannot be anything else");
+            AstVarScope* const vscp = vvtxp->vscp();
+            std::cerr << vscp->fileline()->warnOther()
+                      << "     Example path: " << vscp->prettyName() << endl;
+        }
+    }
+};
+
+//##############################################################################
+// Algorithm implementation
+
+std::unique_ptr buildGraph(const LogicByScope& lbs) {
+    std::unique_ptr graphp{new Graph};
+
+    // AstVarScope::user1() -> VarVertx
+    const VNUser1InUse user1InUse;
+    const auto getVarVertex = [&](AstVarScope* vscp) {
+        if (!vscp->user1p()) vscp->user1p(new VarVertex{graphp.get(), vscp});
+        return vscp->user1u().to();
+    };
+
+    const auto addEdge = [&](V3GraphVertex* fromp, V3GraphVertex* top, int weight, bool cuttable) {
+        new V3GraphEdge{graphp.get(), fromp, top, weight, cuttable};
+    };
+
+    for (const auto& pair : lbs) {
+        AstScope* const scopep = pair.first;
+        AstActive* const activep = pair.second;
+        UASSERT_OBJ(activep->hasCombo(), activep, "Not combinational logic");
+        for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
+            // Can safely ignore Postponed as we generate them all
+            if (VN_IS(nodep, AlwaysPostponed)) continue;
+
+            LogicVertex* const lvtxp = new LogicVertex{graphp.get(), nodep, scopep};
+            const VNUser2InUse user2InUse;
+            const VNUser3InUse user3InUse;
+
+            nodep->foreach([&](AstVarRef* refp) {
+                AstVarScope* const vscp = refp->varScopep();
+                VarVertex* const vvtxp = getVarVertex(vscp);
+                // We want to cut the narrowest signals
+                const int weight = vscp->width() / 8 + 1;
+                // If written, add logic -> var edge
+                if (refp->access().isWriteOrRW() && !vscp->user2SetOnce())
+                    addEdge(lvtxp, vvtxp, weight, true);
+                // If read, add var -> logic edge
+                // Note: Use same heuristic as ordering does to ignore written variables
+                // TODO: Use live variable analysis.
+                if (refp->access().isReadOrRW() && !vscp->user3SetOnce() && !vscp->user2())
+                    addEdge(vvtxp, lvtxp, weight, true);
+            });
+        }
+    }
+
+    return graphp;
+}
+
+void removeNonCyclic(Graph* graphp) {
+    // Work queue
+    std::vector queue;
+
+    const auto enqueue = [&](V3GraphVertex* vtxp) {
+        if (vtxp->user()) return;  // Already in queue
+        vtxp->user(1);
+        queue.push_back(vtxp);
+    };
+
+    // Start with vertices with no inputs or outputs
+    for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
+        if (vtxp->inEmpty() || vtxp->outEmpty()) enqueue(vtxp);
+    }
+
+    // Iterate while we still have candidates
+    while (!queue.empty()) {
+        // Pop next candidate
+        V3GraphVertex* const vtxp = queue.back();
+        queue.pop_back();
+        vtxp->user(0);  // No longer in queue
+
+        if (vtxp->inEmpty()) {
+            // Enqueue children for consideration, remove out edges, and delete this vertex
+            for (V3GraphEdge *edgep = vtxp->outBeginp(), *nextp; edgep; edgep = nextp) {
+                nextp = edgep->outNextp();
+                enqueue(edgep->top());
+                VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
+            }
+            VL_DO_DANGLING(vtxp->unlinkDelete(graphp), vtxp);
+        } else if (vtxp->outEmpty()) {
+            // Enqueue parents for consideration, remove in edges, and delete this vertex
+            for (V3GraphEdge *edgep = vtxp->inBeginp(), *nextp; edgep; edgep = nextp) {
+                nextp = edgep->inNextp();
+                enqueue(edgep->fromp());
+                VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
+            }
+            VL_DO_DANGLING(vtxp->unlinkDelete(graphp), vtxp);
+        }
+    }
+}
+
+// Has this VarVertex been cut? (any edges in or out has been cut)
+bool isCut(const VarVertex* vtxp) {
+    for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+        if (edgep->weight() == 0) return true;
+    }
+    for (V3GraphEdge* edgep = vtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+        if (edgep->weight() == 0) return true;
+    }
+    return false;
+}
+
+std::vector findCutVertices(Graph* graphp) {
+    std::vector result;
+    const VNUser1InUse user1InUse;  // bool: already added to result
+    for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
+        if (VarVertex* const vvtxp = dynamic_cast(vtxp)) {
+            if (!vvtxp->vscp()->user1SetOnce() && isCut(vvtxp)) result.push_back(vvtxp);
+        }
+    }
+    return result;
+}
+
+void resetEdgeWeights(const std::vector& cutVertices) {
+    for (VarVertex* const vvtxp : cutVertices) {
+        for (V3GraphEdge* ep = vvtxp->inBeginp(); ep; ep = ep->inNextp()) ep->weight(1);
+        for (V3GraphEdge* ep = vvtxp->outBeginp(); ep; ep = ep->outNextp()) ep->weight(1);
+    }
+}
+
+// A VarVertex together with its fanout
+using Candidate = std::pair;
+
+// Gather all splitting candidates that are in the same SCC as the given vertex
+void gatherSCCCandidates(V3GraphVertex* vtxp, std::vector& candidates) {
+    if (vtxp->user()) return;  // Already done
+    vtxp->user(true);
+
+    if (VarVertex* const vvtxp = dynamic_cast(vtxp)) {
+        AstVar* const varp = vvtxp->varp();
+        const string name = varp->prettyName();
+        if (!varp->user3SetOnce()  // Only consider each AstVar once
+            && varp->width() != 1  // Ignore 1-bit signals (they cannot be split further)
+            && name.find("__Vdly") == string::npos  // Ignore internal signals
+            && name.find("__Vcell") == string::npos) {
+            // Also compute the fanout of this vertex
+            unsigned fanout = 0;
+            for (V3GraphEdge* ep = vtxp->outBeginp(); ep; ep = ep->outNextp()) ++fanout;
+            candidates.emplace_back(vvtxp, fanout);
+        }
+    }
+
+    // Iterate through all the vertices within the same strongly connected component (same color)
+    for (V3GraphEdge* edgep = vtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+        V3GraphVertex* const top = edgep->top();
+        if (top->color() == vtxp->color()) gatherSCCCandidates(top, candidates);
+    }
+    for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+        V3GraphVertex* const fromp = edgep->fromp();
+        if (fromp->color() == vtxp->color()) gatherSCCCandidates(fromp, candidates);
+    }
+}
+
+// Find all variables in a loop (SCC) that are candidates for splitting to break loops.
+void reportLoopVars(Graph* graphp, VarVertex* vvtxp) {
+    // Vector of variables in UNOPTFLAT loop that are candidates for splitting.
+    std::vector candidates;
+    {
+        // AstNode::user3 is used to mark if we have done a particular variable.
+        // V3GraphVertex::user is used to mark if we have seen this vertex before.
+        const VNUser3InUse user3InUse;
+        graphp->userClearVertices();
+        gatherSCCCandidates(vvtxp, candidates);
+        graphp->userClearVertices();
+    }
+
+    // Possible we only have candidates the user cannot do anything about, so don't bother them.
+    if (candidates.empty()) return;
+
+    // There may be a very large number of candidates, so only report up to 10 of the "most
+    // important" signals.
+    unsigned splittable = 0;
+    const auto reportFirst10 = [&](std::function less) {
+        std::stable_sort(candidates.begin(), candidates.end(), less);
+        for (size_t i = 0; i < 10; i++) {
+            if (i == candidates.size()) break;
+            const Candidate& candidate = candidates[i];
+            AstVar* const varp = candidate.first->varp();
+            std::cerr << V3Error::warnMore() << "    " << varp->fileline() << " "
+                      << varp->prettyName() << ", width " << std::dec << varp->width()
+                      << ", circular fanout " << candidate.second;
+            if (V3SplitVar::canSplitVar(varp)) {
+                std::cerr << ", can split_var";
+                ++splittable;
+            }
+            std::cerr << '\n';
+        }
+    };
+
+    // Widest variables
+    std::cerr << V3Error::warnMore() << "... Widest variables candidate to splitting:\n";
+    reportFirst10([](const Candidate& a, const Candidate& b) {
+        return a.first->varp()->width() > b.first->varp()->width();
+    });
+
+    // Highest fanout
+    std::cerr << V3Error::warnMore() << "... Candidates with the highest fanout:\n";
+    reportFirst10([](const Candidate& a, const Candidate& b) {  //
+        return a.second > b.second;
+    });
+
+    if (splittable) {
+        std::cerr << V3Error::warnMore()
+                  << "... Suggest add /*verilator split_var*/ to appropriate variables above."
+                  << std::endl;
+    }
+    V3Stats::addStat("Scheduling, split_var, candidates", splittable);
+}
+
+void reportCycles(Graph* graphp, const std::vector& cutVertices) {
+    for (VarVertex* vvtxp : cutVertices) {
+        AstVarScope* const vscp = vvtxp->vscp();
+        FileLine* const flp = vscp->fileline();
+
+        // First v3warn not inside warnIsOff so we can see the suppressions with --debug
+        vscp->v3warn(UNOPTFLAT, "Signal unoptimizable: Circular combinational logic: "
+                                    << vscp->prettyNameQ());
+        if (!flp->warnIsOff(V3ErrorCode::UNOPTFLAT) && !flp->lastWarnWaived()) {
+            // Complain just once
+            flp->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true);
+            // Calls Graph::loopsVertexCb
+            graphp->reportLoops(&V3GraphEdge::followAlwaysTrue, vvtxp);
+            if (v3Global.opt.reportUnoptflat()) {
+                // Report candidate variables for splitting
+                reportLoopVars(graphp, vvtxp);
+                // Create a subgraph for the UNOPTFLAT loop
+                V3Graph loopGraph;
+                graphp->subtreeLoops(&V3GraphEdge::followAlwaysTrue, vvtxp, &loopGraph);
+                loopGraph.dumpDotFilePrefixedAlways("unoptflat");
+            }
+        }
+    }
+}
+
+LogicByScope fixCuts(AstNetlist* netlistp, const std::vector& cutVertices) {
+    // For all logic that reads a cut vertex, build a map from logic -> list of cut AstVarScope
+    // they read. Also build a vector of the involved logic for deterministic results.
+    std::unordered_map> lvtx2Cuts;
+    std::vector lvtxps;
+    {
+        const VNUser1InUse user1InUse;  // bool: already added to 'lvtxps'
+        for (VarVertex* const vvtxp : cutVertices) {
+            for (V3GraphEdge* edgep = vvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
+                LogicVertex* const lvtxp = static_cast(edgep->top());
+                if (!lvtxp->logicp()->user1SetOnce()) lvtxps.push_back(lvtxp);
+                lvtx2Cuts[lvtxp].push_back(vvtxp->vscp());
+            }
+        }
+    }
+
+    // Make the logic reading cut vertices use a hybrid sensitivity (combinational, but with some
+    // explicit additional triggers on the cut variables)
+    LogicByScope result;
+    SenTreeFinder finder{netlistp};
+    for (LogicVertex* const lvtxp : lvtxps) {
+        AstNode* const logicp = lvtxp->logicp();
+        logicp->unlinkFrBack();
+        FileLine* const flp = logicp->fileline();
+        // Build the hybrid sensitivity list
+        AstSenItem* senItemsp = nullptr;
+        for (AstVarScope* const vscp : lvtx2Cuts[lvtxp]) {
+            AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ};
+            AstSenItem* const nextp = new AstSenItem{flp, VEdgeType::ET_HYBRID, refp};
+            senItemsp = AstNode::addNext(senItemsp, nextp);
+        }
+        AstSenTree* const senTree = new AstSenTree{flp, senItemsp};
+        // Add logic to result with new sensitivity
+        result.add(lvtxp->scopep(), finder.getSenTree(senTree), logicp);
+        // SenTreeFinder::getSenTree clones, so clean up
+        VL_DO_DANGLING(senTree->deleteTree(), senTree);
+    }
+    return result;
+}
+
+}  // namespace
+
+LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic) {
+    // Build the dataflow (dependency) graph
+    const std::unique_ptr graphp = buildGraph(combinationalLogic);
+
+    // Remove nodes that don't form part of a cycle
+    removeNonCyclic(graphp.get());
+
+    // Nothing to do if no cycles, yay!
+    if (graphp->empty()) return LogicByScope{};
+
+    // Dump for debug
+    if (dumpGraph() >= 6) graphp->dumpDotFilePrefixed("sched-comb-cycles");
+
+    // Make graph acyclic by cutting some edges. Note: This also colors strongly connected
+    // components which reportCycles uses to print each SCCs separately.
+    // TODO: A more optimal algorithm that cuts by removing/marking VarVertex vertices is possible
+    //       Search for "Feedback vertex set" (current algorithm is "Feedback arc set")
+    graphp->acyclic(&V3GraphEdge::followAlwaysTrue);
+
+    // Find all cut vertices
+    const std::vector cutVertices = findCutVertices(graphp.get());
+
+    // Reset edge weights for reporting
+    resetEdgeWeights(cutVertices);
+
+    // Report warnings/diagnostics
+    reportCycles(graphp.get(), cutVertices);
+
+    // Fix cuts by converting dependent logic to use hybrid sensitivities
+    return fixCuts(netlistp, cutVertices);
+}
+
+}  // namespace V3Sched
diff --git a/src/V3SchedPartition.cpp b/src/V3SchedPartition.cpp
new file mode 100644
index 000000000..b356afde1
--- /dev/null
+++ b/src/V3SchedPartition.cpp
@@ -0,0 +1,420 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Scheduling - partitioning
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// V3SchedPartition (and in particular V3Sched::partition) partitions all
+// logic into two regions, the 'act' region contains all logic that might
+// compute a clock via an update even that falls into the SystemVerilog Active
+// scheduling region (that is: blocking and continuous assignments in
+// particular). All other logic is assigned to the 'nba' region.
+//
+// To achieve this, we build a dependency graph of all logic in the design,
+// and trace back from every AstSenItem through all logic that might (via an
+// Active region update) feed into triggering that AstSenItem. Any such logic
+// is then assigned to the 'act' region, and all other logic is assigned to
+// the 'nba' region.
+//
+// For later practical purposes, AstAssignPre logic that would be assigned to
+// the 'act' region is returned separately. Nevertheless, this logic is part of
+// the 'act' region.
+//
+// For more details, please see the internals documentation.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+#include "V3EmitV.h"
+#include "V3Error.h"
+#include "V3Global.h"
+#include "V3Graph.h"
+#include "V3Sched.h"
+
+#include 
+#include 
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+namespace V3Sched {
+
+namespace {
+
+class SchedSenVertex final : public V3GraphVertex {
+    const AstSenItem* const m_senItemp;
+
+public:
+    SchedSenVertex(V3Graph* graphp, const AstSenItem* senItemp)
+        : V3GraphVertex{graphp}
+        , m_senItemp{senItemp} {}
+
+    // LCOV_EXCL_START // Debug code
+    string name() const override {
+        std::ostringstream os;
+        V3EmitV::verilogForTree(const_cast(m_senItemp), os);
+        return os.str();
+    }
+    string dotShape() const override { return "doubleoctagon"; }
+    string dotColor() const override { return "red"; }
+    // LCOV_EXCL_STOP
+};
+
+class SchedLogicVertex final : public V3GraphVertex {
+    AstScope* const m_scopep;
+    AstSenTree* const m_senTreep;
+    AstNode* const m_logicp;
+
+public:
+    SchedLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep, AstNode* logicp)
+        : V3GraphVertex{graphp}
+        , m_scopep{scopep}
+        , m_senTreep{senTreep}
+        , m_logicp{logicp} {}
+    AstScope* scopep() const { return m_scopep; }
+    AstSenTree* senTreep() const { return m_senTreep; }
+    AstNode* logicp() const { return m_logicp; }
+
+    // LCOV_EXCL_START // Debug code
+    string name() const override {
+        return m_logicp->typeName() + ("\n" + m_logicp->fileline()->ascii());
+    };
+    string dotShape() const override { return "rectangle"; }
+    // LCOV_EXCL_STOP
+};
+
+class SchedVarVertex final : public V3GraphVertex {
+    const AstVarScope* const m_vscp;
+
+public:
+    SchedVarVertex(V3Graph* graphp, AstVarScope* vscp)
+        : V3GraphVertex{graphp}
+        , m_vscp{vscp} {}
+
+    // LCOV_EXCL_START // Debug code
+    string name() const override { return m_vscp->name(); }
+    string dotShape() const override {
+        return m_vscp->scopep()->isTop() && m_vscp->varp()->isNonOutput() ? "invhouse" : "ellipse";
+    }
+    string dotColor() const override {
+        return m_vscp->scopep()->isTop() && m_vscp->varp()->isNonOutput() ? "green" : "black";
+    }
+    // LCOV_EXCL_STOP
+};
+
+class SchedGraphBuilder final : public VNVisitor {
+    // NODE STATE
+    // AstVarScope::user1() -> SchedVarVertex
+    // AstSenItem::user1p() -> SchedSenVertex
+    // AstVarScope::user2() -> bool: Read of this AstVarScope triggers this logic.
+    //                         Used only for hybrid logic.
+    const VNUser1InUse m_user1InUse;
+    const VNUser2InUse m_user2InUse;
+
+    // STATE
+    V3Graph* const m_graphp = new V3Graph;  // The dataflow graph being built
+    // The vertices associated with a unique AstSenItem
+    std::unordered_map, SchedSenVertex*> m_senVertices;
+    AstScope* m_scopep = nullptr;  // AstScope of the current AstActive
+    AstSenTree* m_senTreep = nullptr;  // AstSenTree of the current AstActive
+    // Predicate for whether a read of the given variable triggers this block
+    std::function m_readTriggersThisLogic;
+    // The DPI export trigger variable, if any
+    AstVarScope* const m_dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp();
+
+    SchedVarVertex* getVarVertex(AstVarScope* vscp) const {
+        if (!vscp->user1p()) {
+            SchedVarVertex* const vtxp = new SchedVarVertex{m_graphp, vscp};
+            // If this variable can be written via a DPI export, add a source edge from the
+            // DPI export trigger vertex. This ensures calls to DPI exports that might write a
+            // clock end up in the 'act' region.
+            if (vscp->varp()->isWrittenByDpi()) {
+                new V3GraphEdge{m_graphp, getVarVertex(m_dpiExportTriggerp), vtxp, 1};
+            }
+            vscp->user1p(vtxp);
+        }
+        return vscp->user1u().to();
+    }
+
+    SchedSenVertex* getSenVertex(AstSenItem* senItemp) {
+        if (!senItemp->user1p()) {
+            // There is a unique SchedSenVertex for each globally unique AstSenItem. Multiple
+            // AstSenTree might use the same AstSenItem (e.g.: posedge clk1 or rst, posedge clk2 or
+            // rst), so we use a hash map to get the unique SchedSenVertex. (Note: This creates
+            // separate vertices for ET_CHANGED and ET_HYBRID over the same expression, but that is
+            // OK for now).
+            auto it = m_senVertices.find(*senItemp);
+
+            // If it does not exist, create it
+            if (it == m_senVertices.end()) {
+                // Create the vertex
+                SchedSenVertex* const vtxp = new SchedSenVertex{m_graphp, senItemp};
+
+                // Connect up the variable references
+                senItemp->sensp()->foreach([&](AstVarRef* refp) {
+                    new V3GraphEdge{m_graphp, getVarVertex(refp->varScopep()), vtxp, 1};
+                });
+
+                // Store back to hash map so we can find it next time
+                it = m_senVertices.emplace(*senItemp, vtxp).first;
+            }
+
+            // Cache sensitivity vertex
+            senItemp->user1p(it->second);
+        }
+        return senItemp->user1u().to();
+    }
+
+    void visitLogic(AstNode* nodep) {
+        UASSERT_OBJ(m_senTreep, nodep, "Should be under AstActive");
+
+        SchedLogicVertex* const logicVtxp
+            = new SchedLogicVertex{m_graphp, m_scopep, m_senTreep, nodep};
+
+        // Clocked or hybrid logic has explicit sensitivity, so add edge from sensitivity vertex
+        if (!m_senTreep->hasCombo()) {
+            m_senTreep->foreach([=](AstSenItem* senItemp) {
+                if (senItemp->isIllegal()) return;
+                UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), nodep,
+                            "Non-clocked SenItem under clocked SenTree");
+                V3GraphVertex* const eventVtxp = getSenVertex(senItemp);
+                new V3GraphEdge{m_graphp, eventVtxp, logicVtxp, 10};
+            });
+        }
+
+        // Add edges based on references
+        nodep->foreach([=](const AstVarRef* vrefp) {
+            AstVarScope* const vscp = vrefp->varScopep();
+            if (vrefp->access().isReadOrRW() && m_readTriggersThisLogic(vscp)) {
+                new V3GraphEdge{m_graphp, getVarVertex(vscp), logicVtxp, 10};
+            }
+            if (vrefp->access().isWriteOrRW()) {
+                new V3GraphEdge{m_graphp, logicVtxp, getVarVertex(vscp), 10};
+            }
+        });
+
+        // If the logic calls a 'context' DPI import, it might fire the DPI Export trigger
+        if (m_dpiExportTriggerp) {
+            nodep->foreach([=](const AstCCall* callp) {
+                if (!callp->funcp()->dpiImportWrapper()) return;
+                if (!callp->funcp()->dpiContext()) return;
+                new V3GraphEdge{m_graphp, logicVtxp, getVarVertex(m_dpiExportTriggerp), 10};
+            });
+        }
+    }
+
+    // VISIT methods
+    void visit(AstActive* nodep) override {
+        AstSenTree* const senTreep = nodep->sensesp();
+        UASSERT_OBJ(senTreep->hasClocked() || senTreep->hasCombo() || senTreep->hasHybrid(), nodep,
+                    "Unhandled");
+        UASSERT_OBJ(!m_senTreep, nodep, "Should not nest");
+
+        // Mark explicit sensitivities as not triggering these blocks
+        if (senTreep->hasHybrid()) {
+            AstNode::user2ClearTree();
+            senTreep->foreach([](const AstVarRef* refp) {  //
+                refp->varScopep()->user2(true);
+            });
+        }
+
+        m_senTreep = senTreep;
+        iterateChildrenConst(nodep);
+        m_senTreep = nullptr;
+    }
+
+    void visit(AstNodeProcedure* nodep) override { visitLogic(nodep); }
+    void visit(AstNodeAssign* nodep) override { visitLogic(nodep); }
+    void visit(AstCoverToggle* nodep) override { visitLogic(nodep); }
+    void visit(AstAlwaysPublic* nodep) override { visitLogic(nodep); }
+
+    // Pre and Post logic are handled separately
+    void visit(AstAssignPre* nodep) override {}
+    void visit(AstAssignPost* nodep) override {}
+    void visit(AstAlwaysPost* nodep) override {}
+
+    // LCOV_EXCL_START
+    // Ignore
+    void visit(AstInitialStatic* nodep) override { nodep->v3fatalSrc("Should not need ordering"); }
+    void visit(AstInitial* nodep) override {  //
+        nodep->v3fatalSrc("Should not need ordering");
+    }
+    void visit(AstFinal* nodep) override {  //
+        nodep->v3fatalSrc("Should not need ordering");
+    }
+
+    // Default - Any other AstActive content not handled above will hit this
+    void visit(AstNode* nodep) override {  //
+        nodep->v3fatalSrc("Should behandled above");
+    }
+    // LCOV_EXCL_STOP
+
+    SchedGraphBuilder(const LogicByScope& clockedLogic, const LogicByScope& combinationalLogic,
+                      const LogicByScope& hybridLogic) {
+        // Build the data flow graph
+        const auto iter = [this](const LogicByScope& lbs) {
+            for (const auto& pair : lbs) {
+                m_scopep = pair.first;
+                iterate(pair.second);
+                m_scopep = nullptr;
+            }
+        };
+        // Clocked logic is never triggered by reads
+        m_readTriggersThisLogic = [](AstVarScope*) { return false; };
+        iter(clockedLogic);
+        // Combinational logic is always triggered by reads
+        m_readTriggersThisLogic = [](AstVarScope*) { return true; };
+        iter(combinationalLogic);
+        // Hybrid logic is triggered by all reads, except for reads of the explicit sensitivities
+        m_readTriggersThisLogic = [](AstVarScope* vscp) { return !vscp->user2(); };
+        iter(hybridLogic);
+    }
+
+public:
+    // Build the dataflow graph for partitioning
+    static std::unique_ptr build(const LogicByScope& clockedLogic,
+                                          const LogicByScope& combinationalLogic,
+                                          const LogicByScope& hybridLogic) {
+        SchedGraphBuilder visitor{clockedLogic, combinationalLogic, hybridLogic};
+        return std::unique_ptr{visitor.m_graphp};
+    }
+};
+
+void colorActiveRegion(const V3Graph& graph) {
+    // Work queue for depth first traversal
+    std::vector queue{};
+
+    // Trace from all SchedSenVertex
+    for (V3GraphVertex* vtxp = graph.verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
+        if (const auto activeEventVtxp = dynamic_cast(vtxp)) {
+            queue.push_back(activeEventVtxp);
+        }
+    }
+
+    // Depth first traversal
+    while (!queue.empty()) {
+        // Pop next work item
+        V3GraphVertex& vtx = *queue.back();
+        queue.pop_back();
+        // If not first encounter, move on
+        if (vtx.color() != 0) continue;
+
+        // Mark vertex as being in active region
+        vtx.color(1);
+
+        // Enqueue all parent vertices that feed this vertex.
+        for (V3GraphEdge* edgep = vtx.inBeginp(); edgep; edgep = edgep->inNextp()) {
+            queue.push_back(edgep->fromp());
+        }
+
+        // If this is a logic vertex, also enqueue all variable vertices that are driven from this
+        // logic. This will ensure that if a variable is set in the active region, then all
+        // settings of that variable will be in the active region.
+        if (dynamic_cast(&vtx)) {
+            for (V3GraphEdge* edgep = vtx.outBeginp(); edgep; edgep = edgep->outNextp()) {
+                UASSERT(dynamic_cast(edgep->top()), "Should be var vertex");
+                queue.push_back(edgep->top());
+            }
+        }
+    }
+}
+
+}  // namespace
+
+LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
+                       LogicByScope& hybridLogic) {
+    UINFO(2, __FUNCTION__ << ": " << endl);
+
+    // Build the graph
+    const std::unique_ptr graphp
+        = SchedGraphBuilder::build(clockedLogic, combinationalLogic, hybridLogic);
+    if (dumpGraph() >= 6) graphp->dumpDotFilePrefixed("sched");
+
+    // Partition into Active and NBA regions
+    colorActiveRegion(*(graphp.get()));
+    if (dumpGraph() >= 6) graphp->dumpDotFilePrefixed("sched-partitioned", true);
+
+    LogicRegions result;
+
+    for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
+        if (const auto lvtxp = dynamic_cast(vtxp)) {
+            LogicByScope& lbs = lvtxp->color() ? result.m_act : result.m_nba;
+            AstNode* const logicp = lvtxp->logicp();
+            logicp->unlinkFrBack();
+            lbs.add(lvtxp->scopep(), lvtxp->senTreep(), logicp);
+        }
+    }
+
+    // Partition the Pre logic
+    {
+        const VNUser1InUse user1InUse;  // AstVarScope::user1() -> bool: read in Active region
+        const VNUser2InUse user2InUse;  // AstVarScope::user2() -> bool: writen in Active region
+
+        const auto markVars = [](AstNode* nodep) {
+            nodep->foreach([](const AstNodeVarRef* vrefp) {
+                AstVarScope* const vscp = vrefp->varScopep();
+                if (vrefp->access().isReadOrRW()) vscp->user1(true);
+                if (vrefp->access().isWriteOrRW()) vscp->user2(true);
+            });
+        };
+
+        for (const auto& pair : result.m_act) {
+            AstActive* const activep = pair.second;
+            markVars(activep->sensesp());
+            markVars(activep);
+        }
+
+        // AstAssignPre, AstAssignPost and AstAlwaysPost should only appear under a clocked
+        // AstActive, and should be the only thing left at this point.
+        for (const auto& pair : clockedLogic) {
+            AstScope* const scopep = pair.first;
+            AstActive* const activep = pair.second;
+            for (AstNode *nodep = activep->stmtsp(), *nextp; nodep; nodep = nextp) {
+                nextp = nodep->nextp();
+                if (AstAssignPre* const logicp = VN_CAST(nodep, AssignPre)) {
+                    bool toActiveRegion = false;
+                    logicp->foreach([&](const AstNodeVarRef* vrefp) {
+                        AstVarScope* const vscp = vrefp->varScopep();
+                        if (vrefp->access().isReadOnly()) {
+                            // Variable only read in Pre, and is written in active region
+                            if (vscp->user2()) toActiveRegion = true;
+                        } else {
+                            // Variable written in Pre, and referenced in active region
+                            if (vscp->user1() || vscp->user2()) toActiveRegion = true;
+                        }
+                    });
+                    LogicByScope& lbs = toActiveRegion ? result.m_pre : result.m_nba;
+                    logicp->unlinkFrBack();
+                    lbs.add(scopep, activep->sensesp(), logicp);
+                } else {
+                    UASSERT_OBJ(VN_IS(nodep, AssignPost) || VN_IS(nodep, AlwaysPost), nodep,
+                                "Unexpected node type " << nodep->typeName());
+                    nodep->unlinkFrBack();
+                    result.m_nba.add(scopep, activep->sensesp(), nodep);
+                }
+            }
+        }
+    }
+
+    // Clean up remains of inputs
+    clockedLogic.deleteActives();
+    combinationalLogic.deleteActives();
+    hybridLogic.deleteActives();
+
+    return result;
+}
+
+}  // namespace V3Sched
diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp
new file mode 100644
index 000000000..7c3d3fb3c
--- /dev/null
+++ b/src/V3SchedReplicate.cpp
@@ -0,0 +1,275 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Scheduling - replicate combinational logic
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// Combinational (including hybrid) logic driven from both the 'act' and 'nba'
+// region needs to be re-evaluated even if only one of those regions updates
+// an input variable. We achieve this by replicating such combinational logic
+// in both the 'act' and 'nba' regions.
+//
+// Furthermore we also replicate all combinational logic driven from a top
+// level input into a separate 'ico' (Input Combinational) region which is
+// executed at the beginning of the time step. This allows us to change both
+// data and clock signals during the same 'eval' call while maintaining the
+// combinational invariant required by V3Order.
+//
+// The implementation is a simple graph algorithm, where we build a dependency
+// graph of all logic in the design, and then propagate the driving region
+// information through it. We then replicate any logic into its additional
+// driving regions.
+//
+// For more details, please see the internals documentation.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+#include "V3Error.h"
+#include "V3Graph.h"
+#include "V3Sched.h"
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+namespace V3Sched {
+
+namespace {
+
+// Driving region flags
+enum RegionFlags : uint8_t {
+    NONE = 0x0,  //
+    INPUT = 0x1,  // Variable/logic is driven from top level input
+    ACTIVE = 0x2,  // Variable/logic is driven from 'act' region logic
+    NBA = 0x4  // Variable/logic is driven from 'nba' region logic
+};
+
+//##############################################################################
+// Data structures (graph types)
+
+class Vertex VL_NOT_FINAL : public V3GraphVertex {
+    RegionFlags m_drivingRegions{NONE};  // The regions driving this vertex
+
+public:
+    explicit Vertex(V3Graph* graphp)
+        : V3GraphVertex{graphp} {}
+    uint8_t drivingRegions() const { return m_drivingRegions; }
+    void addDrivingRegions(uint8_t regions) {
+        m_drivingRegions = static_cast(m_drivingRegions | regions);
+    }
+
+    // LCOV_EXCL_START // Debug code
+    string dotColor() const override {
+        switch (static_cast(m_drivingRegions)) {
+        case NONE: return "black";
+        case INPUT: return "red";
+        case ACTIVE: return "green";
+        case NBA: return "blue";
+        case INPUT | ACTIVE: return "yellow";
+        case INPUT | NBA: return "magenta";
+        case ACTIVE | NBA: return "cyan";
+        case INPUT | ACTIVE | NBA: return "gray80";  // don't want white on white background
+        default: v3fatal("There are only 3 region bits"); return "";
+        }
+    }
+    // LCOV_EXCL_STOP
+};
+
+class LogicVertex final : public Vertex {
+    AstScope* const m_scopep;  // The enclosing AstScope of the logic node
+    AstSenTree* const m_senTreep;  // The sensitivity of the logic node
+    AstNode* const m_logicp;  // The logic node this vertex represents
+    RegionFlags const m_assignedRegion;  // The region this logic is originally assigned to
+
+public:
+    LogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep, AstNode* logicp,
+                RegionFlags assignedRegion)
+        : Vertex{graphp}
+        , m_scopep{scopep}
+        , m_senTreep{senTreep}
+        , m_logicp{logicp}
+        , m_assignedRegion{assignedRegion} {
+        addDrivingRegions(assignedRegion);
+    }
+    AstScope* scopep() const { return m_scopep; }
+    AstSenTree* senTreep() const { return m_senTreep; }
+    AstNode* logicp() const { return m_logicp; }
+    RegionFlags assignedRegion() const { return m_assignedRegion; }
+
+    // For graph dumping
+    string name() const override { return m_logicp->fileline()->ascii(); };
+    string dotShape() const override { return "rectangle"; }
+};
+
+class VarVertex final : public Vertex {
+    AstVarScope* const m_vscp;  // The AstVarScope this vertex represents
+
+public:
+    VarVertex(V3Graph* graphp, AstVarScope* vscp)
+        : Vertex{graphp}
+        , m_vscp{vscp} {
+        // Top level inputs are
+        if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()) {
+            addDrivingRegions(INPUT);
+        }
+        // Currently we always execute suspendable processes at the beginning of
+        // the act region, which means combinational logic driven from a suspendable
+        // processes must be present in the 'act' region
+        if (varp()->isWrittenBySuspendable()) addDrivingRegions(ACTIVE);
+    }
+    AstVarScope* vscp() const { return m_vscp; }
+    AstVar* varp() const { return m_vscp->varp(); }
+    AstScope* scopep() const { return m_vscp->scopep(); }
+
+    // For graph dumping
+    string name() const override { return m_vscp->name(); }
+    string dotShape() const override { return varp()->isPrimaryInish() ? "invhouse" : "ellipse"; }
+};
+
+class Graph final : public V3Graph {};
+
+//##############################################################################
+// Algorithm implementation
+
+std::unique_ptr buildGraph(const LogicRegions& logicRegions) {
+    std::unique_ptr graphp{new Graph};
+
+    // AstVarScope::user1() -> VarVertx
+    const VNUser1InUse user1InUse;
+    const auto getVarVertex = [&](AstVarScope* vscp) {
+        if (!vscp->user1p()) vscp->user1p(new VarVertex{graphp.get(), vscp});
+        return vscp->user1u().to();
+    };
+
+    const auto addEdge = [&](Vertex* fromp, Vertex* top) {
+        new V3GraphEdge{graphp.get(), fromp, top, 1};
+    };
+
+    const auto addLogic = [&](RegionFlags region, AstScope* scopep, AstActive* activep) {
+        AstSenTree* const senTreep = activep->sensesp();
+
+        // Predicate for whether a read of the given variable triggers this block
+        std::function readTriggersThisLogic;
+
+        const VNUser4InUse user4InUse;  // bool: Explicit sensitivity of hybrid logic just below
+
+        if (senTreep->hasClocked()) {
+            // Clocked logic is never triggered by reads
+            readTriggersThisLogic = [](AstVarScope*) { return false; };
+        } else if (senTreep->hasCombo()) {
+            // Combinational logic is always triggered by reads
+            readTriggersThisLogic = [](AstVarScope*) { return true; };
+        } else {
+            UASSERT_OBJ(senTreep->hasHybrid(), activep, "unexpected");
+            // Hybrid logic is triggered by all reads, except for reads of the explicit
+            // sensitivities
+            readTriggersThisLogic = [](AstVarScope* vscp) { return !vscp->user4(); };
+            senTreep->foreach([](const AstVarRef* refp) {  //
+                refp->varScopep()->user4(true);
+            });
+        }
+
+        for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
+            LogicVertex* const lvtxp
+                = new LogicVertex{graphp.get(), scopep, senTreep, nodep, region};
+            const VNUser2InUse user2InUse;
+            const VNUser3InUse user3InUse;
+
+            nodep->foreach([&](AstVarRef* refp) {
+                AstVarScope* const vscp = refp->varScopep();
+                VarVertex* const vvtxp = getVarVertex(vscp);
+
+                // If read, add var -> logic edge
+                // Note: Use same heuristic as ordering does to ignore written variables
+                // TODO: Use live variable analysis.
+                if (refp->access().isReadOrRW() && !vscp->user3SetOnce()
+                    && readTriggersThisLogic(vscp) && !vscp->user2()) {  //
+                    addEdge(vvtxp, lvtxp);
+                }
+                // If written, add logic -> var edge
+                // Note: See V3Order for why AlwaysPostponed is safe to be ignored. We ignore it
+                // as otherwise we would end up with a false cycle.
+                if (refp->access().isWriteOrRW() && !vscp->user2SetOnce()
+                    && !VN_IS(nodep, AlwaysPostponed)) {  //
+                    addEdge(lvtxp, vvtxp);
+                }
+            });
+        }
+    };
+
+    for (const auto& pair : logicRegions.m_pre) addLogic(ACTIVE, pair.first, pair.second);
+    for (const auto& pair : logicRegions.m_act) addLogic(ACTIVE, pair.first, pair.second);
+    for (const auto& pair : logicRegions.m_nba) addLogic(NBA, pair.first, pair.second);
+
+    return graphp;
+}
+
+void propagateDrivingRegions(Vertex* vtxp) {
+    // Note: The graph is always acyclic, so the recursion will terminate
+
+    // Nothing to do if already visited
+    if (vtxp->user()) return;
+
+    // Compute union of driving regions of all inputs
+    uint8_t drivingRegions = 0;
+    for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+        Vertex* const srcp = static_cast(edgep->fromp());
+        propagateDrivingRegions(srcp);
+        drivingRegions |= srcp->drivingRegions();
+    }
+
+    // Add any new driving regions
+    vtxp->addDrivingRegions(drivingRegions);
+
+    // Mark as visited
+    vtxp->user(true);
+}
+
+LogicReplicas replicate(Graph* graphp) {
+    LogicReplicas result;
+    for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
+        if (LogicVertex* const lvtxp = dynamic_cast(vtxp)) {
+            const auto replicateTo = [&](LogicByScope& lbs) {
+                lbs.add(lvtxp->scopep(), lvtxp->senTreep(), lvtxp->logicp()->cloneTree(false));
+            };
+            const uint8_t targetRegions = lvtxp->drivingRegions() & ~lvtxp->assignedRegion();
+            UASSERT(!lvtxp->senTreep()->hasClocked() || targetRegions == 0,
+                    "replicating clocked logic");
+            if (targetRegions & INPUT) replicateTo(result.m_ico);
+            if (targetRegions & ACTIVE) replicateTo(result.m_act);
+            if (targetRegions & NBA) replicateTo(result.m_nba);
+        }
+    }
+    return result;
+}
+
+}  // namespace
+
+LogicReplicas replicateLogic(LogicRegions& logicRegionsRegions) {
+    // Build the dataflow (dependency) graph
+    const std::unique_ptr graphp = buildGraph(logicRegionsRegions);
+    // Dump for debug
+    if (dumpGraph() >= 6) graphp->dumpDotFilePrefixed("sched-replicate");
+    // Propagate driving region flags
+    for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
+        propagateDrivingRegions(static_cast(vtxp));
+    }
+    // Dump for debug
+    if (dumpGraph() >= 6) graphp->dumpDotFilePrefixed("sched-replicate-propagated");
+    // Replicate the necessary logic
+    return replicate(graphp.get());
+}
+
+}  // namespace V3Sched
diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp
new file mode 100644
index 000000000..de8845b43
--- /dev/null
+++ b/src/V3SchedTiming.cpp
@@ -0,0 +1,391 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Code scheduling
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+//
+// Functions defined in this file are used by V3Sched.cpp to properly integrate
+// static scheduling with timing features. They create external domains for
+// variables, remap them to trigger vectors, and create timing resume/commit
+// calls for the global eval loop. There is also a function that transforms
+// forks into emittable constructs.
+//
+// See the internals documentation docs/internals.rst for more details.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3EmitCBase.h"
+#include "V3Error.h"
+#include "V3Sched.h"
+
+#include 
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+namespace V3Sched {
+
+//============================================================================
+// Remaps external domains using the specified trigger map
+
+std::map>
+TimingKit::remapDomains(const std::unordered_map& trigMap) const {
+    std::map> remappedDomainMap;
+    for (const auto& vscpDomains : m_externalDomains) {
+        const AstVarScope* const vscp = vscpDomains.first;
+        const auto& domains = vscpDomains.second;
+        auto& remappedDomains = remappedDomainMap[vscp];
+        remappedDomains.reserve(domains.size());
+        for (AstSenTree* const domainp : domains) {
+            remappedDomains.push_back(trigMap.at(domainp));
+        }
+    }
+    return remappedDomainMap;
+}
+
+//============================================================================
+// Creates a timing resume call (if needed, else returns null)
+
+AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
+    if (!m_resumeFuncp) {
+        if (m_lbs.empty()) return nullptr;
+        // Create global resume function
+        AstScope* const scopeTopp = netlistp->topScopep()->scopep();
+        m_resumeFuncp = new AstCFunc{netlistp->fileline(), "_timing_resume", scopeTopp, ""};
+        m_resumeFuncp->dontCombine(true);
+        m_resumeFuncp->isLoose(true);
+        m_resumeFuncp->isConst(false);
+        m_resumeFuncp->declPrivate(true);
+        scopeTopp->addBlocksp(m_resumeFuncp);
+        for (auto& p : m_lbs) {
+            // Put all the timing actives in the resume function
+            AstActive* const activep = p.second;
+            m_resumeFuncp->addStmtsp(activep);
+        }
+    }
+    return new AstCCall{m_resumeFuncp->fileline(), m_resumeFuncp};
+}
+
+//============================================================================
+// Creates a timing commit call (if needed, else returns null)
+
+AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
+    if (!m_commitFuncp) {
+        for (auto& p : m_lbs) {
+            AstActive* const activep = p.second;
+            auto* const resumep = VN_AS(activep->stmtsp(), CMethodHard);
+            UASSERT_OBJ(!resumep->nextp(), resumep, "Should be the only statement here");
+            AstVarScope* const schedulerp = VN_AS(resumep->fromp(), VarRef)->varScopep();
+            UASSERT_OBJ(schedulerp->dtypep()->basicp()->isDelayScheduler()
+                            || schedulerp->dtypep()->basicp()->isTriggerScheduler()
+                            || schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler(),
+                        schedulerp, "Unexpected type");
+            if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue;
+            // Create the global commit function only if we have trigger schedulers
+            if (!m_commitFuncp) {
+                AstScope* const scopeTopp = netlistp->topScopep()->scopep();
+                m_commitFuncp
+                    = new AstCFunc{netlistp->fileline(), "_timing_commit", scopeTopp, ""};
+                m_commitFuncp->dontCombine(true);
+                m_commitFuncp->isLoose(true);
+                m_commitFuncp->isConst(false);
+                m_commitFuncp->declPrivate(true);
+                scopeTopp->addBlocksp(m_commitFuncp);
+            }
+            AstSenTree* const sensesp = activep->sensesp();
+            FileLine* const flp = sensesp->fileline();
+            // Negate the sensitivity. We will commit only if the event wasn't triggered on the
+            // current iteration
+            auto* const negSensesp = sensesp->cloneTree(false);
+            negSensesp->sensesp()->sensp(
+                new AstLogNot{flp, negSensesp->sensesp()->sensp()->unlinkFrBack()});
+            sensesp->addNextHere(negSensesp);
+            auto* const newactp = new AstActive{flp, "", negSensesp};
+            // Create the commit call and put it in the commit function
+            auto* const commitp = new AstCMethodHard{
+                flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "commit"};
+            if (resumep->pinsp()) commitp->addPinsp(resumep->pinsp()->cloneTree(false));
+            commitp->statement(true);
+            commitp->dtypeSetVoid();
+            newactp->addStmtsp(commitp);
+            m_commitFuncp->addStmtsp(newactp);
+        }
+        // We still haven't created a commit function (no trigger schedulers), return null
+        if (!m_commitFuncp) return nullptr;
+    }
+    return new AstCCall{m_commitFuncp->fileline(), m_commitFuncp};
+}
+
+//============================================================================
+// Creates the timing kit and marks variables written by suspendables
+
+TimingKit prepareTiming(AstNetlist* const netlistp) {
+    if (!v3Global.usesTiming()) return {};
+    class AwaitVisitor final : public VNVisitor {
+    private:
+        // NODE STATE
+        //  AstSenTree::user1()  -> bool.  Set true if the sentree has been visited.
+        const VNUser1InUse m_inuser1;
+
+        // STATE
+        bool m_inProcess = false;  // Are we in a process?
+        bool m_gatherVars = false;  // Should we gather vars in m_writtenBySuspendable?
+        AstScope* const m_scopeTopp;  // Scope at the top
+        LogicByScope& m_lbs;  // Timing resume actives
+        AstNodeStmt*& m_postUpdatesr;  // Post updates for the trigger eval function
+        // Additional var sensitivities
+        std::map>& m_externalDomains;
+        std::set m_processDomains;  // Sentrees from the current process
+        // Variables written by suspendable processes
+        std::vector m_writtenBySuspendable;
+
+        // METHODS
+        // Create an active with a timing scheduler resume() call
+        void createResumeActive(AstCAwait* const awaitp) {
+            auto* const methodp = VN_AS(awaitp->exprp(), CMethodHard);
+            AstVarScope* const schedulerp = VN_AS(methodp->fromp(), VarRef)->varScopep();
+            AstSenTree* const sensesp = awaitp->sensesp();
+            FileLine* const flp = sensesp->fileline();
+            // Create a resume() call on the timing scheduler
+            auto* const resumep = new AstCMethodHard{
+                flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "resume"};
+            resumep->statement(true);
+            resumep->dtypeSetVoid();
+            if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) {
+                if (methodp->pinsp()) resumep->addPinsp(methodp->pinsp()->cloneTree(false));
+            } else if (schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler()) {
+                auto* const postp = resumep->cloneTree(false);
+                postp->name("doPostUpdates");
+                m_postUpdatesr = AstNode::addNext(m_postUpdatesr, postp);
+            }
+            // Put it in an active and put that in the global resume function
+            auto* const activep = new AstActive{flp, "_timing", sensesp};
+            activep->addStmtsp(resumep);
+            m_lbs.emplace_back(m_scopeTopp, activep);
+        }
+
+        // VISITORS
+        void visit(AstNodeProcedure* const nodep) override {
+            UASSERT_OBJ(!m_inProcess && !m_gatherVars && m_processDomains.empty()
+                            && m_writtenBySuspendable.empty(),
+                        nodep, "Process in process?");
+            m_inProcess = true;
+            m_gatherVars = nodep->isSuspendable();  // Only gather vars in a suspendable
+            const VNUser2InUse user2InUse;  // AstVarScope -> bool: Set true if var has been added
+                                            // to m_writtenBySuspendable
+            iterateChildren(nodep);
+            for (AstVarScope* const vscp : m_writtenBySuspendable) {
+                m_externalDomains[vscp].insert(m_processDomains.begin(), m_processDomains.end());
+                vscp->varp()->setWrittenBySuspendable();
+            }
+            m_processDomains.clear();
+            m_writtenBySuspendable.clear();
+            m_inProcess = false;
+            m_gatherVars = false;
+        }
+        void visit(AstFork* nodep) override {
+            VL_RESTORER(m_gatherVars);
+            if (m_inProcess) m_gatherVars = true;
+            // If not in a process, we don't need to gather variables or domains
+            iterateChildren(nodep);
+        }
+        void visit(AstCAwait* nodep) override {
+            if (AstSenTree* const sensesp = nodep->sensesp()) {
+                if (!sensesp->user1SetOnce()) createResumeActive(nodep);
+                nodep->clearSensesp();  // Clear as these sentrees will get deleted later
+                if (m_inProcess) m_processDomains.insert(sensesp);
+            }
+        }
+        void visit(AstNodeVarRef* nodep) override {
+            if (m_gatherVars && nodep->access().isWriteOrRW()
+                && !nodep->varScopep()->user2SetOnce()) {
+                m_writtenBySuspendable.push_back(nodep->varScopep());
+            }
+        }
+
+        //--------------------
+        void visit(AstNodeMath*) override {}  // Accelerate
+        void visit(AstNode* nodep) override { iterateChildren(nodep); }
+
+    public:
+        // CONSTRUCTORS
+        explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs, AstNodeStmt*& postUpdatesr,
+                              std::map>& externalDomains)
+            : m_scopeTopp{nodep->topScopep()->scopep()}
+            , m_lbs{lbs}
+            , m_postUpdatesr{postUpdatesr}
+            , m_externalDomains{externalDomains} {
+            iterate(nodep);
+        }
+        ~AwaitVisitor() override = default;
+    };
+    LogicByScope lbs;
+    AstNodeStmt* postUpdates = nullptr;
+    std::map> externalDomains;
+    AwaitVisitor{netlistp, lbs, postUpdates, externalDomains};
+    return {std::move(lbs), postUpdates, std::move(externalDomains)};
+}
+
+//============================================================================
+// Visits all forks and transforms their sub-statements into separate functions.
+
+void transformForks(AstNetlist* const netlistp) {
+    if (!v3Global.usesTiming()) return;
+    // Transform all forked processes into functions
+    class ForkVisitor final : public VNVisitor {
+    private:
+        // NODE STATE
+        //  AstVar::user1()  -> bool.  Set true if the variable was declared before the current
+        //                             fork.
+        const VNUser1InUse m_inuser1;
+
+        // STATE
+        bool m_inClass = false;  // Are we in a class?
+        bool m_beginHasAwaits = false;  // Does the current begin have awaits?
+        AstFork* m_forkp = nullptr;  // Current fork
+        AstCFunc* m_funcp = nullptr;  // Current function
+
+        // METHODS
+        // Remap local vars referenced by the given fork function
+        // TODO: We should only pass variables to the fork that are
+        // live in the fork body, but for that we need a proper data
+        // flow analysis framework which we don't have at the moment
+        void remapLocals(AstCFunc* const funcp, AstCCall* const callp) {
+            const VNUser2InUse user2InUse;  // AstVarScope -> AstVarScope: var to remap to
+            funcp->foreach([&](AstNodeVarRef* refp) {
+                AstVar* const varp = refp->varp();
+                AstBasicDType* const dtypep = varp->dtypep()->basicp();
+                // If it a fork sync or an intra-assignment variable, pass it by value
+                const bool passByValue = (dtypep && dtypep->isForkSync())
+                                         || VString::startsWith(varp->name(), "__Vintra");
+                // Only handle vars passed by value or locals declared before the fork
+                if (!passByValue && (!varp->user1() || !varp->isFuncLocal())) return;
+                if (passByValue) {
+                    // We can just pass it to the new function
+                } else if (m_forkp->joinType().join()) {
+                    // If it's fork..join, we can refer to variables from the parent process
+                    if (!m_funcp->user1SetOnce()) {  // Only do this once per function
+                        // Move all locals to the heap before the fork
+                        auto* const awaitp = new AstCAwait{
+                            m_forkp->fileline(), new AstCStmt{m_forkp->fileline(), "VlNow{}"}};
+                        awaitp->statement(true);
+                        m_forkp->addHereThisAsNext(awaitp);
+                    }
+                } else {
+                    refp->v3warn(E_UNSUPPORTED, "Unsupported: variable local to a forking process "
+                                                "accessed in a fork..join_any or fork..join_none");
+                    return;
+                }
+                // Remap the reference
+                AstVarScope* const vscp = refp->varScopep();
+                if (!vscp->user2p()) {
+                    // Clone the var to the new function
+                    AstVar* const varp = refp->varp();
+                    AstVar* const newvarp
+                        = new AstVar{varp->fileline(), VVarType::BLOCKTEMP, varp->name(), varp};
+                    newvarp->funcLocal(true);
+                    newvarp->direction(passByValue ? VDirection::INPUT : VDirection::REF);
+                    funcp->addArgsp(newvarp);
+                    AstVarScope* const newvscp
+                        = new AstVarScope{newvarp->fileline(), funcp->scopep(), newvarp};
+                    funcp->scopep()->addVarsp(newvscp);
+                    vscp->user2p(newvscp);
+                    callp->addArgsp(new AstVarRef{refp->fileline(), vscp, VAccess::READ});
+                }
+                auto* const newvscp = VN_AS(vscp->user2p(), VarScope);
+                refp->varScopep(newvscp);
+                refp->varp(newvscp->varp());
+            });
+        }
+
+        // VISITORS
+        void visit(AstNodeModule* nodep) override {
+            VL_RESTORER(m_inClass);
+            m_inClass = VN_IS(nodep, Class);
+            iterateChildren(nodep);
+        }
+        void visit(AstCFunc* nodep) override {
+            m_funcp = nodep;
+            iterateChildren(nodep);
+            m_funcp = nullptr;
+        }
+        void visit(AstVar* nodep) override {
+            if (!m_forkp) nodep->user1(true);
+        }
+        void visit(AstFork* nodep) override {
+            if (m_forkp) return;  // Handle forks in forks after moving them to new functions
+            VL_RESTORER(m_forkp);
+            m_forkp = nodep;
+            iterateChildrenConst(nodep);  // Const, so we don't iterate the calls twice
+            // Replace self with the function calls (no co_await, as we don't want the main
+            // process to suspend whenever any of the children do)
+            // V3Dead could have removed all statements from the fork, so guard against it
+            AstNode* const stmtsp = nodep->stmtsp();
+            if (stmtsp) nodep->addNextHere(stmtsp->unlinkFrBackWithNext());
+            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
+        }
+        void visit(AstBegin* nodep) override {
+            UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork");
+            // Start with children, so later we only find awaits that are actually in this begin
+            m_beginHasAwaits = false;
+            iterateChildrenConst(nodep);
+            if (m_beginHasAwaits) {
+                UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name");
+                // Create a function to put this begin's statements in
+                FileLine* const flp = nodep->fileline();
+                AstCFunc* const newfuncp
+                    = new AstCFunc{flp, nodep->name(), m_funcp->scopep(), "VlCoroutine"};
+                m_funcp->addNextHere(newfuncp);
+                newfuncp->isLoose(m_funcp->isLoose());
+                newfuncp->slow(m_funcp->slow());
+                newfuncp->isConst(m_funcp->isConst());
+                newfuncp->declPrivate(true);
+                // Replace the begin with a call to the newly created function
+                auto* const callp = new AstCCall{flp, newfuncp};
+                nodep->replaceWith(callp);
+                // If we're in a class, add a vlSymsp arg
+                if (m_inClass) {
+                    newfuncp->addInitsp(new AstCStmt{nodep->fileline(), "VL_KEEP_THIS;\n"});
+                    newfuncp->argTypes(EmitCBaseVisitor::symClassVar());
+                    callp->argTypes("vlSymsp");
+                }
+                // Put the begin's statements in the function, delete the begin
+                newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext());
+                remapLocals(newfuncp, callp);
+            } else {
+                // No awaits, just inline the forked process
+                nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext());
+            }
+            VL_DO_DANGLING(nodep->deleteTree(), nodep);
+        }
+        void visit(AstCAwait* nodep) override {
+            m_beginHasAwaits = true;
+            iterateChildrenConst(nodep);
+        }
+
+        //--------------------
+        void visit(AstNodeMath*) override {}  // Accelerate
+        void visit(AstNode* nodep) override { iterateChildren(nodep); }
+
+    public:
+        // CONSTRUCTORS
+        explicit ForkVisitor(AstNetlist* nodep) { iterate(nodep); }
+        ~ForkVisitor() override = default;
+    };
+    ForkVisitor{netlistp};
+    V3Global::dumpCheckGlobalTree("sched_forks", 0, dumpTree() >= 6);
+}
+
+}  // namespace V3Sched
diff --git a/src/V3Scope.cpp b/src/V3Scope.cpp
index c0694e2ab..ce60b19de 100644
--- a/src/V3Scope.cpp
+++ b/src/V3Scope.cpp
@@ -43,6 +43,7 @@ class ScopeVisitor final : public VNVisitor {
 private:
     // NODE STATE
     // AstVar::user1p           -> AstVarScope replacement for this variable
+    // AstCell::user2p          -> AstScope*.  The scope created inside the cell
     // AstTask::user2p          -> AstTask*.  Replacement task
     const VNUser1InUse m_inuser1;
     const VNUser2InUse m_inuser2;
@@ -125,6 +126,10 @@ private:
                     AstNodeModule* const modp = cellp->modp();
                     UASSERT_OBJ(modp, cellp, "Unlinked mod");
                     iterate(modp);  // Recursive call to visit(AstNodeModule)
+                    if (VN_IS(modp, Iface)) {
+                        // Remember newly created scope
+                        cellp->user2p(m_scopep);
+                    }
                 }
             }
         }
@@ -261,7 +266,12 @@ private:
     void visit(AstVar* nodep) override {
         // Make new scope variable
         if (!nodep->user1p()) {
-            AstVarScope* const varscp = new AstVarScope(nodep->fileline(), m_scopep, nodep);
+            AstScope* scopep = m_scopep;
+            if (AstIfaceRefDType* const ifacerefp = VN_CAST(nodep->dtypep(), IfaceRefDType)) {
+                // Attach every non-virtual interface variable its inner scope
+                if (ifacerefp->cellp()) scopep = VN_AS(ifacerefp->cellp()->user2p(), Scope);
+            }
+            AstVarScope* const varscp = new AstVarScope{nodep->fileline(), scopep, nodep};
             UINFO(6, "   New scope " << varscp << endl);
             if (m_aboveCellp && !m_aboveCellp->isTrace()) varscp->trace(false);
             nodep->user1p(varscp);
@@ -280,15 +290,11 @@ private:
         // VarRef needs to point to VarScope
         // Make sure variable has made user1p.
         UASSERT_OBJ(nodep->varp(), nodep, "Unlinked");
-        if (nodep->varp()->isIfaceRef()) {
-            nodep->varScopep(nullptr);
-        } else {
-            // We may have not made the variable yet, and we can't make it now as
-            // the var's referenced package etc might not be created yet.
-            // So push to a list and post-correct.
-            // No check here for nodep->classOrPackagep(), will check when walk list.
-            m_varRefScopes.emplace(nodep, m_scopep);
-        }
+        // We may have not made the variable yet, and we can't make it now as
+        // the var's referenced package etc might not be created yet.
+        // So push to a list and post-correct.
+        // No check here for nodep->classOrPackagep(), will check when walk list.
+        m_varRefScopes.emplace(nodep, m_scopep);
     }
     void visit(AstScopeName* nodep) override {
         // If there's a %m in the display text, we add a special node that will contain the name()
diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h
new file mode 100644
index 000000000..97b31e5b2
--- /dev/null
+++ b/src/V3SenExprBuilder.h
@@ -0,0 +1,253 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Builder for sensitivity checking expressions.
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+//*************************************************************************
+
+#ifndef VERILATOR_V3SENEXPRBUILDER_H_
+#define VERILATOR_V3SENEXPRBUILDER_H_
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Ast.h"
+#include "V3UniqueNames.h"
+
+//######################################################################
+// SenExprBuilder constructs the expressions used to compute if an
+// AstSenTree have triggered
+
+class SenExprBuilder final {
+    // STATE
+    AstScope* const m_scopep;  // The scope
+
+    std::vector m_locals;  // Trigger eval local variables
+    std::vector m_inits;  // Initialization statements for prevoius values
+    std::vector m_preUpdates;  // Pre update assignments
+    std::vector m_postUpdates;  // Post update assignments
+
+    std::unordered_map, AstVarScope*> m_prev;  // The 'previous value' signals
+    std::unordered_map, AstVarScope*> m_curr;  // The 'current value' signals
+    std::unordered_set> m_hasPreUpdate;  // Whether the given sen expression already
+                                                        // has an update statement in m_preUpdates
+    std::unordered_set> m_hasPostUpdate;  // Likewis for m_postUpdates
+
+    V3UniqueNames m_currNames{"__Vtrigcurrexpr"};  // For generating unique current value
+                                                   // signal names
+    V3UniqueNames m_prevNames{"__Vtrigprevexpr"};  // Likewise for previous values
+
+    static bool isSupportedDType(AstNodeDType* dtypep) {
+        dtypep = dtypep->skipRefp();
+        if (VN_IS(dtypep, BasicDType)) return true;
+        if (VN_IS(dtypep, PackArrayDType)) return true;
+        if (VN_IS(dtypep, UnpackArrayDType)) return isSupportedDType(dtypep->subDTypep());
+        if (VN_IS(dtypep, NodeUOrStructDType)) return true;  // All are packed at the moment
+        return false;
+    }
+
+    static bool isSimpleExpr(const AstNode* const exprp) {
+        return exprp->forall([](const AstNode* const nodep) {
+            return VN_IS(nodep, Const) || VN_IS(nodep, NodeVarRef) || VN_IS(nodep, Sel)
+                   || VN_IS(nodep, NodeSel) || VN_IS(nodep, MemberSel)
+                   || VN_IS(nodep, CMethodHard);
+        });
+    }
+
+    // METHODS
+    AstNode* getCurr(AstNode* exprp) {
+        // For simple expressions like varrefs or selects, just use them directly
+        if (isSimpleExpr(exprp)) return exprp->cloneTree(false);
+
+        // Create the 'current value' variable
+        FileLine* const flp = exprp->fileline();
+        auto result = m_curr.emplace(*exprp, nullptr);
+        if (result.second) {
+            AstVar* const varp
+                = new AstVar{flp, VVarType::BLOCKTEMP, m_currNames.get(exprp), exprp->dtypep()};
+            varp->funcLocal(true);
+            m_locals.push_back(varp);
+            AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp};
+            m_scopep->addVarsp(vscp);
+            result.first->second = vscp;
+        }
+        AstVarScope* const currp = result.first->second;
+
+        // Add pre update if it does not exist yet in this round
+        if (m_hasPreUpdate.emplace(*currp).second) {
+            m_preUpdates.push_back(new AstAssign{flp, new AstVarRef{flp, currp, VAccess::WRITE},
+                                                 exprp->cloneTree(false)});
+        }
+        return new AstVarRef{flp, currp, VAccess::READ};
+    }
+    AstVarScope* getPrev(AstNode* exprp) {
+        FileLine* const flp = exprp->fileline();
+        const auto rdCurr = [=]() { return getCurr(exprp); };
+
+        // Create the 'previous value' variable
+        auto it = m_prev.find(*exprp);
+        if (it == m_prev.end()) {
+            // For readability, use the scoped signal name if the trigger is a simple AstVarRef
+            string name;
+            if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) {
+                AstVarScope* vscp = refp->varScopep();
+                name = "__Vtrigrprev__" + vscp->scopep()->nameDotless() + "__"
+                       + vscp->varp()->name();
+            } else {
+                name = m_prevNames.get(exprp);
+            }
+
+            AstVarScope* prevp;
+            if (m_scopep->isTop()) {
+                prevp = m_scopep->createTemp(name, exprp->dtypep());
+            } else {
+                AstVar* const varp = new AstVar{flp, VVarType::BLOCKTEMP, m_prevNames.get(exprp),
+                                                exprp->dtypep()};
+                varp->funcLocal(true);
+                m_locals.push_back(varp);
+                prevp = new AstVarScope{flp, m_scopep, varp};
+                m_scopep->addVarsp(prevp);
+            }
+            it = m_prev.emplace(*exprp, prevp).first;
+
+            // Add the initializer init
+            AstAssign* const initp = new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE},
+                                                   exprp->cloneTree(false)};
+            m_inits.push_back(initp);
+        }
+
+        AstVarScope* const prevp = it->second;
+
+        const auto wrPrev = [=]() { return new AstVarRef{flp, prevp, VAccess::WRITE}; };
+
+        // Add post update if it does not exist yet
+        if (m_hasPostUpdate.emplace(*exprp).second) {
+            if (!isSupportedDType(exprp->dtypep())) {
+                exprp->v3warn(E_UNSUPPORTED,
+                              "Unsupported: Cannot detect changes on expression of complex type"
+                              " (see combinational cycles reported by UNOPTFLAT)");
+                return prevp;
+            }
+
+            if (AstUnpackArrayDType* const dtypep = VN_CAST(exprp->dtypep(), UnpackArrayDType)) {
+                AstCMethodHard* const cmhp = new AstCMethodHard{flp, wrPrev(), "assign", rdCurr()};
+                cmhp->dtypeSetVoid();
+                cmhp->statement(true);
+                m_postUpdates.push_back(cmhp);
+            } else {
+                m_postUpdates.push_back(new AstAssign{flp, wrPrev(), rdCurr()});
+            }
+        }
+
+        return prevp;
+    }
+
+    std::pair createTerm(AstSenItem* senItemp) {
+        FileLine* const flp = senItemp->fileline();
+        AstNode* const senp = senItemp->sensp();
+
+        const auto currp = [=]() { return getCurr(senp); };
+        const auto prevp = [=]() { return new AstVarRef{flp, getPrev(senp), VAccess::READ}; };
+        const auto lsb = [=](AstNodeMath* opp) { return new AstSel{flp, opp, 0, 1}; };
+
+        // All event signals should be 1-bit at this point
+        switch (senItemp->edgeType()) {
+        case VEdgeType::ET_ILLEGAL:
+            return {nullptr, false};  // We already warn for this in V3LinkResolve
+        case VEdgeType::ET_CHANGED:
+        case VEdgeType::ET_HYBRID:  //
+            if (VN_IS(senp->dtypep(), UnpackArrayDType)) {
+                AstCMethodHard* const resultp = new AstCMethodHard{flp, currp(), "neq", prevp()};
+                resultp->dtypeSetBit();
+                return {resultp, true};
+            }
+            return {new AstNeq{flp, currp(), prevp()}, true};
+        case VEdgeType::ET_BOTHEDGE:  //
+            return {lsb(new AstXor{flp, currp(), prevp()}), false};
+        case VEdgeType::ET_POSEDGE:  //
+            return {lsb(new AstAnd{flp, currp(), new AstNot{flp, prevp()}}), false};
+        case VEdgeType::ET_NEGEDGE:  //
+            return {lsb(new AstAnd{flp, new AstNot{flp, currp()}, prevp()}), false};
+        case VEdgeType::ET_EVENT: {
+            UASSERT_OBJ(v3Global.hasEvents(), senItemp, "Inconsistent");
+            {
+                // If the event is fired, set up the clearing process
+                AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"};
+                callp->dtypeSetBit();
+                AstIf* const ifp = new AstIf{flp, callp};
+                m_postUpdates.push_back(ifp);
+
+                // Clear 'fired' state when done
+                AstCMethodHard* const clearp = new AstCMethodHard{flp, currp(), "clearFired"};
+                ifp->addThensp(clearp);
+                clearp->dtypeSetVoid();
+                clearp->statement(true);
+
+                // Enqueue for clearing 'triggered' state on next eval
+                AstTextBlock* const blockp = new AstTextBlock{flp};
+                ifp->addThensp(blockp);
+                const auto add = [&](const string& text) { blockp->addText(flp, text, true); };
+                add("vlSymsp->enqueueTriggeredEventForClearing(");
+                blockp->addNodesp(currp());
+                add(");\n");
+            }
+
+            // Get 'fired' state
+            AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"};
+            callp->dtypeSetBit();
+            return {callp, false};
+        }
+        case VEdgeType::ET_TRUE:  //
+            return {currp(), false};
+        default:  // LCOV_EXCL_START
+            senItemp->v3fatalSrc("Unknown edge type");
+            return {nullptr, false};
+        }  // LCOV_EXCL_STOP
+    }
+
+public:
+    // Returns the expression computing the trigger, and a bool indicating that
+    // this trigger should be fired on the first evaluation (at initialization)
+    std::pair build(const AstSenTree* senTreep) {
+        FileLine* const flp = senTreep->fileline();
+        AstNode* resultp = nullptr;
+        bool firedAtInitialization = false;
+        for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
+             senItemp = VN_AS(senItemp->nextp(), SenItem)) {
+            const auto& pair = createTerm(senItemp);
+            if (AstNode* const termp = pair.first) {
+                resultp = resultp ? new AstOr{flp, resultp, termp} : termp;
+                firedAtInitialization |= pair.second;
+            }
+        }
+        return {resultp, firedAtInitialization};
+    }
+
+    std::vector getAndClearInits() { return std::move(m_inits); }
+    std::vector getAndClearLocals() { return std::move(m_locals); }
+
+    std::vector getAndClearPreUpdates() {
+        m_hasPreUpdate.clear();
+        return std::move(m_preUpdates);
+    }
+
+    std::vector getAndClearPostUpdates() {
+        m_hasPostUpdate.clear();
+        return std::move(m_postUpdates);
+    }
+
+    // CONSTRUCTOR
+    SenExprBuilder(AstScope* scopep)
+        : m_scopep{scopep} {}
+};
+
+#endif  // Guard
diff --git a/src/V3SenTree.h b/src/V3SenTree.h
index 636476b6f..698406c74 100644
--- a/src/V3SenTree.h
+++ b/src/V3SenTree.h
@@ -36,9 +36,20 @@ private:
     // STATE
     AstTopScope* const m_topScopep;  // Top scope to add global SenTrees to
     std::unordered_set> m_trees;  // Set of global SenTrees
+    AstSenTree* m_combop = nullptr;  // The unique combinational domain SenTree
+    AstSenTree* m_initialp = nullptr;  // The unique initial domain SenTree
 
     VL_UNCOPYABLE(SenTreeFinder);
 
+    template   //
+    AstSenTree* makeUnique() {
+        FileLine* const fl = m_topScopep->fileline();
+        AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, T_Domain{}}};
+        AstSenTree* const restultp = getSenTree(senTreep);
+        VL_DO_DANGLING(senTreep->deleteTree(), senTreep);  // getSenTree clones, so can delete
+        return restultp;
+    }
+
 public:
     // CONSTRUCTORS
     SenTreeFinder()
@@ -50,6 +61,8 @@ public:
         for (AstSenTree* senTreep = m_topScopep->senTreesp(); senTreep;
              senTreep = VN_AS(senTreep->nextp(), SenTree)) {
             m_trees.emplace(*senTreep);
+            if (senTreep->hasCombo()) m_combop = senTreep;
+            if (senTreep->hasInitial()) m_initialp = senTreep;
         }
     }
 
@@ -72,11 +85,15 @@ public:
     // Return the global combinational AstSenTree.
     // If no such global SenTree exists create one and add it to the stored AstTopScope.
     AstSenTree* getComb() {
-        FileLine* const fl = m_topScopep->fileline();
-        AstSenTree* const combp = new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Combo()}};
-        AstSenTree* const resultp = getSenTree(combp);
-        VL_DO_DANGLING(combp->deleteTree(), combp);  // getSenTree clones, so can delete
-        return resultp;
+        if (!m_combop) m_combop = makeUnique();
+        return m_combop;
+    }
+
+    // Return the global initial AstSenTree.
+    // If no such global SenTree exists create one and add it to the stored AstTopScope.
+    AstSenTree* getInitial() {
+        if (!m_initialp) m_initialp = makeUnique();
+        return m_initialp;
     }
 };
 
diff --git a/src/V3Split.cpp b/src/V3Split.cpp
index e6521ea69..f5828b111 100644
--- a/src/V3Split.cpp
+++ b/src/V3Split.cpp
@@ -426,6 +426,10 @@ protected:
             UINFO(9, "         NotSplittable " << nodep << endl);
             scoreboardPli(nodep);
         }
+        if (nodep->isTimingControl()) {
+            UINFO(9, "         NoReordering " << nodep << endl);
+            m_noReorderWhy = "TimingControl";
+        }
         iterateChildren(nodep);
     }
 
diff --git a/src/V3Stats.h b/src/V3Stats.h
index 7fb43db33..1a51619c6 100644
--- a/src/V3Stats.h
+++ b/src/V3Stats.h
@@ -75,13 +75,13 @@ class V3Statistic final {
     bool m_printit = true;  ///< Print the results
 public:
     // METHODS
-    string stage() const { return m_stage; }
-    string name() const { return m_name; }
-    double count() const { return m_count; }
-    bool sumit() const { return m_sumit; }
-    bool perf() const { return m_perf; }
-    bool printit() const { return m_printit; }
-    virtual void dump(std::ofstream& os) const;
+    string stage() const VL_MT_SAFE { return m_stage; }
+    string name() const VL_MT_SAFE { return m_name; }
+    double count() const VL_MT_SAFE { return m_count; }
+    bool sumit() const VL_MT_SAFE { return m_sumit; }
+    bool perf() const VL_MT_SAFE { return m_perf; }
+    bool printit() const VL_MT_SAFE { return m_printit; }
+    virtual void dump(std::ofstream& os) const VL_MT_SAFE;
     void combineWith(V3Statistic* otherp) {
         m_count += otherp->count();
         otherp->m_printit = false;
diff --git a/src/V3StdFuture.h b/src/V3StdFuture.h
index 32a4f7539..ea630b7fd 100644
--- a/src/V3StdFuture.h
+++ b/src/V3StdFuture.h
@@ -17,6 +17,8 @@
 #ifndef VERILATOR_V3STDFUTURE_H_
 #define VERILATOR_V3STDFUTURE_H_
 
+#include 
+
 namespace vlstd {
 
 // constexpr std::max with arguments passed by value (required by constexpr before C++14)
@@ -25,6 +27,17 @@ constexpr T max(T a, T b) {
     return a > b ? a : b;
 }
 
+// C++17 is_invocable
+template 
+struct is_invocable
+    : std::is_constructible,
+                            std::reference_wrapper::type>> {};
+
+// C++17 is_invocable_r
+template 
+struct is_invocable_r
+    : std::is_constructible,
+                            std::reference_wrapper::type>> {};
 };  // namespace vlstd
 
 #endif  // Guard
diff --git a/src/V3String.cpp b/src/V3String.cpp
index 6f4aa473b..af6122e7d 100644
--- a/src/V3String.cpp
+++ b/src/V3String.cpp
@@ -183,6 +183,11 @@ bool VString::startsWith(const string& str, const string& prefix) {
     return str.rfind(prefix, 0) == 0;  // Faster than .find(_) == 0
 }
 
+bool VString::endsWith(const string& str, const string& suffix) {
+    if (str.length() < suffix.length()) return false;
+    return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
+}
+
 //######################################################################
 // VHashSha256
 
diff --git a/src/V3String.h b/src/V3String.h
index 1fbcecaf7..7aacdb150 100644
--- a/src/V3String.h
+++ b/src/V3String.h
@@ -116,6 +116,8 @@ public:
     static string replaceWord(const string& str, const string& from, const string& to);
     // Predicate to check if 'str' starts with 'prefix'
     static bool startsWith(const string& str, const string& prefix);
+    // Predicate to check if 'str' ends with 'suffix'
+    static bool endsWith(const string& str, const string& suffix);
 };
 
 //######################################################################
diff --git a/src/V3Task.cpp b/src/V3Task.cpp
index 1dd4c2cd1..0d1eb2c22 100644
--- a/src/V3Task.cpp
+++ b/src/V3Task.cpp
@@ -175,7 +175,7 @@ private:
         // Find all var->varscope mappings, for later cleanup
         for (AstNode* stmtp = nodep->varsp(); stmtp; stmtp = stmtp->nextp()) {
             if (AstVarScope* const vscp = VN_CAST(stmtp, VarScope)) {
-                if (vscp->varp()->isFuncLocal()) {
+                if (vscp->varp()->isFuncLocal() || vscp->varp()->isUsedLoopIdx()) {
                     UINFO(9, "   funcvsc " << vscp << endl);
                     m_varToScopeMap.insert(
                         std::make_pair(std::make_pair(nodep, vscp->varp()), vscp));
@@ -362,6 +362,7 @@ private:
     AstScope* m_scopep = nullptr;  // Current scope
     InsertMode m_insMode = IM_BEFORE;  // How to insert
     AstNode* m_insStmtp = nullptr;  // Where to insert statement
+    bool m_inSensesp = false;  // Are we under a senitem?
     int m_modNCalls = 0;  // Incrementing func # for making symbols
     DpiCFuncs m_dpiNames;  // Map of all created DPI functions
 
@@ -407,7 +408,7 @@ private:
 
     // Replace varrefs with new var pointer
     void relink(AstNode* nodep) {
-        nodep->foreachAndNext([](AstVarRef* refp) {
+        nodep->foreachAndNext([](AstVarRef* refp) {
             if (refp->varp()->user2p()) {  // It's being converted to an alias.
                 AstVarScope* const newvscp = VN_AS(refp->varp()->user2p(), VarScope);
                 refp->varScopep(newvscp);
@@ -1057,18 +1058,19 @@ private:
         }
     }
 
-    AstVarScope* makeDpiExporTrigger() {
-        AstVarScope* dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp();
+    AstVarScope* getDpiExporTrigger() {
+        AstNetlist* const netlistp = v3Global.rootp();
+        AstVarScope* dpiExportTriggerp = netlistp->dpiExportTriggerp();
         if (!dpiExportTriggerp) {
             // Create the global DPI export trigger flag the first time we encounter a DPI export.
             // This flag is set any time a DPI export is invoked, and cleared at the end of eval.
             FileLine* const fl = m_topScopep->fileline();
-            AstVar* const varp
-                = new AstVar{fl, VVarType::VAR, "__Vdpi_export_trigger", VFlagBitPacked{}, 1};
+            const string name{"__Vdpi_export_trigger"};
+            AstVar* const varp = new AstVar{fl, VVarType::VAR, name, VFlagBitPacked{}, 1};
             m_topScopep->scopep()->modp()->addStmtsp(varp);
             dpiExportTriggerp = new AstVarScope{fl, m_topScopep->scopep(), varp};
             m_topScopep->scopep()->addVarsp(dpiExportTriggerp);
-            v3Global.rootp()->dpiExportTriggerp(dpiExportTriggerp);
+            netlistp->dpiExportTriggerp(dpiExportTriggerp);
         }
         return dpiExportTriggerp;
     }
@@ -1272,52 +1274,37 @@ private:
             // Mark all non-local variables written by the DPI exported function as being updated
             // by DPI exports. This ensures correct ordering and change detection later.
 
-            // Gather non-local variables written by the exported function
-            std::vector writtenps;
-            {
-                const VNUser5InUse user5InUse;  // AstVarScope::user5 -> Already added variable
-                cfuncp->foreach([&writtenps](AstVarRef* refp) {
-                    if (refp->access().isReadOnly()) return;  // Ignore read reference
-                    AstVarScope* const varScopep = refp->varScopep();
-                    if (varScopep->user5()) return;  // Ignore already added variable
-                    varScopep->user5(true);  // Mark as already added
-                    // Note: We are ignoring function locals as they should not be referenced
-                    // anywhere outside of the enclosing AstCFunc, and therefore they are
-                    // irrelevant for code ordering. This is an optimization to avoid adding
-                    // useless nodes to the ordering graph in V3Order.
-                    if (varScopep->varp()->isFuncLocal()) return;
-                    writtenps.push_back(varScopep);
-                });
-            }
+            // Mark non-local variables written by the exported function
+            bool writesNonLocals = false;
+            cfuncp->foreach([&writesNonLocals](AstVarRef* refp) {
+                if (refp->access().isReadOnly()) return;  // Ignore read reference
+                AstVar* const varp = refp->varScopep()->varp();
+                // We are ignoring function locals as they should not be referenced anywhere
+                // outside the enclosing AstCFunc, hence they are irrelevant for code ordering.
+                if (varp->isFuncLocal()) return;
+                // Mark it as written by DPI export
+                varp->setWrittenByDpi();
+                // Remember we had some
+                writesNonLocals = true;
+            });
 
-            if (!writtenps.empty()) {
-                AstVarScope* const dpiExportTriggerp = makeDpiExporTrigger();
-                FileLine* const fl = cfuncp->fileline();
+            // If this DPI export writes some non-local variables, set the DPI Export Trigger flag
+            // in the function.
+            if (writesNonLocals) {
+                AstVarScope* const dpiExportTriggerp = getDpiExporTrigger();
+                FileLine* const flp = cfuncp->fileline();
 
                 // Set DPI export trigger flag every time the DPI export is called.
                 AstAssign* const assignp
-                    = new AstAssign{fl, new AstVarRef{fl, dpiExportTriggerp, VAccess::WRITE},
-                                    new AstConst{fl, AstConst::BitTrue{}}};
+                    = new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerp, VAccess::WRITE},
+                                    new AstConst{flp, AstConst::BitTrue{}}};
+
                 // Add as first statement (to avoid issues with early returns) to exported function
                 if (cfuncp->stmtsp()) {
                     cfuncp->stmtsp()->addHereThisAsNext(assignp);
                 } else {
                     cfuncp->addStmtsp(assignp);
                 }
-
-                // Add an always block sensitive to the DPI export trigger flag, and add an
-                // AstDpiExportUpdated node under it for each variable that are writen by the
-                // exported function.
-                AstAlways* const alwaysp = new AstAlways{
-                    fl, VAlwaysKwd::ALWAYS,
-                    new AstSenTree{
-                        fl, new AstSenItem{fl, VEdgeType::ET_HIGHEDGE,
-                                           new AstVarRef{fl, dpiExportTriggerp, VAccess::READ}}},
-                    nullptr};
-                for (AstVarScope* const varScopep : writtenps) {
-                    alwaysp->addStmtsp(new AstDpiExportUpdated{fl, varScopep});
-                }
-                m_scopep->addBlocksp(alwaysp);
             }
         }
 
@@ -1384,6 +1371,11 @@ private:
         m_scopep = nullptr;
     }
     void visit(AstNodeFTaskRef* nodep) override {
+        if (m_inSensesp) {
+            nodep->v3warn(E_UNSUPPORTED, "Unsupported: function calls in sensitivity lists");
+            nodep->taskp(nullptr);  // So V3Broken doesn't complain
+            return;
+        }
         // Includes handling AstMethodCall, AstNew
         UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked?");
         iterateIntoFTask(nodep->taskp());  // First, do hierarchical funcs
@@ -1536,6 +1528,12 @@ private:
         iterateChildren(nodep);
         m_insStmtp = nullptr;  // Next thing should be new statement
     }
+    void visit(AstSenItem* nodep) override {
+        UASSERT_OBJ(!m_inSensesp, nodep, "Senitem under senitem?");
+        VL_RESTORER(m_inSensesp);
+        m_inSensesp = true;
+        iterateChildren(nodep);
+    }
     //--------------------
     void visit(AstNode* nodep) override { iterateChildren(nodep); }
 
diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp
new file mode 100644
index 000000000..8f657e0b0
--- /dev/null
+++ b/src/V3Timing.cpp
@@ -0,0 +1,784 @@
+// -*- mode: C++; c-file-style: "cc-mode" -*-
+//*************************************************************************
+// DESCRIPTION: Verilator: Prepare AST for timing features
+//
+// Code available from: https://verilator.org
+//
+//*************************************************************************
+//
+// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
+// can redistribute it and/or modify it under the terms of either the GNU
+// Lesser General Public License Version 3 or the Perl Artistic License
+// Version 2.0.
+// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+//
+// TimingVisitor transformations:
+// - for each intra-assignment timing control:
+//     - if it's a continuous assignment, transform it into an always
+//     - introduce an intermediate variable
+//     - write the original RHS to the intermediate variable before the timing control
+//     - write the intermediate variable to the original LHS after the timing control
+// - for each delay:
+//     - scale it according to the module's timescale
+//     - replace it with a CAwait statement waiting on the global delay scheduler (with the
+//       specified delay value)
+//       - if there is no global delay scheduler (see verilated_timing.{h,cpp}), create it
+// - for each event control:
+//     - if there is no corresponding trigger scheduler (see verilated_timing.{h,cpp}), create it
+//     - replace with a CAwait statement waiting on the corresponding trigger scheduler
+// - for each wait(cond) statement:
+//     - replace it with a loop like: while (!cond) @()
+// - for each fork:
+//     - put each statement in a begin if it isn't in one already
+//     - if it's not a fork..join_none:
+//         - create a join sync variable
+//         - create statements that sync the main process with its children
+// - for each process or C++ function, if it has CAwait statements, mark it as suspendable
+//     - if we mark a virtual function as suspendable, mark all overriding and overridden functions
+//     as suspendable, as well as calling processes
+//
+// See the internals documentation docs/internals.rst for more details.
+//
+//*************************************************************************
+
+#include "config_build.h"
+#include "verilatedos.h"
+
+#include "V3Timing.h"
+
+#include "V3Ast.h"
+#include "V3Const.h"
+#include "V3EmitV.h"
+#include "V3Graph.h"
+#include "V3SenExprBuilder.h"
+#include "V3SenTree.h"
+#include "V3UniqueNames.h"
+
+VL_DEFINE_DEBUG_FUNCTIONS;
+
+// ######################################################################
+//  Transform nodes affected by timing
+
+class TimingVisitor final : public VNVisitor {
+private:
+    // TYPES
+    // Vertex of a dependency graph of suspendable nodes, e.g. if a node (process or task) is
+    // suspendable, all its dependents should also be suspendable
+    class DependencyVertex final : public V3GraphVertex {
+        AstNode* const m_nodep;  // AST node represented by this graph vertex
+        // ACCESSORS
+        string name() const override {
+            return cvtToHex(nodep()) + ' ' + nodep()->prettyTypeName();
+        }
+        FileLine* fileline() const override { return nodep()->fileline(); }
+        string dotColor() const override { return nodep()->user2() ? "red" : "black"; }
+
+    public:
+        // CONSTRUCTORS
+        DependencyVertex(V3Graph* graphp, AstNode* nodep)
+            : V3GraphVertex{graphp}
+            , m_nodep{nodep} {}
+        ~DependencyVertex() override = default;
+
+        // ACCESSORS
+        virtual AstNode* nodep() const { return m_nodep; }
+    };
+
+    // NODE STATE
+    //  AstNode::user1()                         -> bool.         Set true if the node has been
+    //                                                            processed.
+    //  AstSenTree::user1()                      -> AstVarScope*. Trigger scheduler assigned
+    //                                                            to this sentree
+    //  Ast{NodeProcedure,CFunc,Begin}::user2()  -> bool.         Set true if process/task is
+    //                                                            suspendable
+    //  AstSenTree::user2()                      -> AstText*.     Debug info passed to the
+    //                                                            timing schedulers
+    //  Ast{NodeProcedure,CFunc,Begin}::user3()  -> DependencyVertex*.  Vertex in m_depGraph
+    const VNUser1InUse m_user1InUse;
+    const VNUser2InUse m_user2InUse;
+    const VNUser3InUse m_user3InUse;
+
+    // STATE
+    // Current context
+    AstNetlist* const m_netlistp;  // Root node
+    AstScope* const m_scopeTopp = m_netlistp->topScopep()->scopep();  // Scope at the top
+    AstClass* m_classp = nullptr;  // Current class
+    AstScope* m_scopep = nullptr;  // Current scope
+    AstActive* m_activep = nullptr;  // Current active
+    AstNode* m_procp = nullptr;  // NodeProcedure/CFunc/Fork we're under
+    double m_timescaleFactor = 1.0;  // Factor to scale delays by
+
+    // Unique names
+    V3UniqueNames m_contAssignVarNames{"__VassignWtmp"};  // Names for temp AssignW vars
+    V3UniqueNames m_intraValueNames{"__Vintraval"};  // Intra assign delay value var names
+    V3UniqueNames m_intraIndexNames{"__Vintraidx"};  // Intra assign delay index var names
+    V3UniqueNames m_intraLsbNames{"__Vintralsb"};  // Intra assign delay LSB var names
+    V3UniqueNames m_forkNames{"__Vfork"};  // Fork name generator
+    V3UniqueNames m_trigSchedNames{"__VtrigSched"};  // Trigger scheduler name generator
+    V3UniqueNames m_dynTrigNames{"__VdynTrigger"};  // Dynamic trigger name generator
+
+    // DTypes
+    AstBasicDType* m_forkDtp = nullptr;  // Fork variable type
+    AstBasicDType* m_trigSchedDtp = nullptr;  // Trigger scheduler type
+
+    // Timing-related globals
+    AstVarScope* m_delaySchedp = nullptr;  // Global delay scheduler
+    AstVarScope* m_dynamicSchedp = nullptr;  // Global dynamic trigger scheduler
+    AstSenTree* m_delaySensesp = nullptr;  // Domain to trigger if a delayed coroutine is resumed
+    AstSenTree* m_dynamicSensesp = nullptr;  // Domain to trigger if a dynamic trigger is set
+
+    // Other
+    V3Graph m_depGraph;  // Dependency graph where a node is a dependency of another if it being
+                         // suspendable makes the other node suspendable
+    SenTreeFinder m_finder{m_netlistp};  // Sentree finder and uniquifier
+
+    // METHODS
+    // Get or create the dependency vertex for the given node
+    DependencyVertex* getDependencyVertex(AstNode* const nodep) {
+        if (!nodep->user3p()) nodep->user3p(new DependencyVertex{&m_depGraph, nodep});
+        return nodep->user3u().to();
+    }
+    // Find net delay on the LHS of an assignment
+    AstDelay* getLhsNetDelay(AstNodeAssign* nodep) const {
+        bool foundWrite = false;
+        AstDelay* delayp = nullptr;
+        nodep->lhsp()->foreach([&](const AstNodeVarRef* const refp) {
+            if (!refp->access().isWriteOrRW()) return;
+            UASSERT_OBJ(!foundWrite, nodep, "Should only be one variable written to on the LHS");
+            foundWrite = true;
+            if (refp->varp()->delayp()) {
+                delayp = refp->varp()->delayp();
+                delayp->unlinkFrBack();
+            }
+        });
+        return delayp;
+    }
+    // Transform an assignment with an intra timing control into a timing control with the
+    // assignment under it
+    AstNodeStmt* factorOutTimingControl(AstNodeAssign* nodep) const {
+        AstNodeStmt* stmtp = nodep;
+        AstDelay* delayp = getLhsNetDelay(nodep);
+        FileLine* const flp = nodep->fileline();
+        AstNode* const controlp = nodep->timingControlp();
+        if (controlp) {
+            controlp->unlinkFrBack();
+            if (auto* const assignDelayp = VN_CAST(controlp, Delay)) {
+                if (delayp) {
+                    delayp->lhsp(new AstAdd{flp, delayp->lhsp()->unlinkFrBack(),
+                                            assignDelayp->lhsp()->unlinkFrBack()});
+                    VL_DO_DANGLING(assignDelayp->deleteTree(), nodep);
+                } else {
+                    delayp = assignDelayp;
+                }
+            }
+        }
+        if (delayp) {
+            stmtp->replaceWith(delayp);
+            delayp->addStmtsp(stmtp);
+            stmtp = delayp;
+        }
+        if (auto* const sensesp = VN_CAST(controlp, SenTree)) {
+            auto* const eventControlp = new AstEventControl{flp, sensesp, nullptr};
+            stmtp->replaceWith(eventControlp);
+            eventControlp->addStmtsp(stmtp);
+            stmtp = eventControlp;
+        }
+        return stmtp == nodep ? nullptr : stmtp;
+    }
+    // Calculate the factor to scale delays by
+    double calculateTimescaleFactor(VTimescale timeunit) const {
+        int scalePowerOfTen = timeunit.powerOfTen() - m_netlistp->timeprecision().powerOfTen();
+        return std::pow(10.0, scalePowerOfTen);
+    }
+    // Construct SenItems from VarRefs in an expression
+    AstSenItem* varRefpsToSenItemsp(AstNode* const nodep) const {
+        AstNode* senItemsp = nullptr;
+        const VNUser4InUse user4InUse;
+        nodep->foreach([&](AstNodeVarRef* refp) {
+            if (refp->access().isWriteOnly()) return;
+            AstVarScope* const vscp = refp->varScopep();
+            if (vscp->user4SetOnce()) return;
+            const bool isEvent = vscp->dtypep() && vscp->dtypep()->basicp()
+                                 && vscp->dtypep()->basicp()->isEvent();
+            const auto edgeType = isEvent ? VEdgeType::ET_EVENT : VEdgeType::ET_CHANGED;
+            senItemsp = AstNode::addNext(
+                senItemsp, new AstSenItem{refp->fileline(), edgeType,
+                                          new AstVarRef{refp->fileline(), vscp, VAccess::READ}});
+        });
+        return VN_AS(senItemsp, SenItem);
+    }
+    // Creates the global delay scheduler variable
+    AstVarScope* getCreateDelayScheduler() {
+        if (m_delaySchedp) return m_delaySchedp;
+        auto* const dlySchedDtp = new AstBasicDType{
+            m_scopeTopp->fileline(), VBasicDTypeKwd::DELAY_SCHEDULER, VSigning::UNSIGNED};
+        m_netlistp->typeTablep()->addTypesp(dlySchedDtp);
+        m_delaySchedp = m_scopeTopp->createTemp("__VdlySched", dlySchedDtp);
+        // Delay scheduler has to be accessible from top
+        m_delaySchedp->varp()->sigPublic(true);
+        m_netlistp->delaySchedulerp(m_delaySchedp->varp());
+        return m_delaySchedp;
+    }
+    // Creates the delay sentree
+    AstSenTree* getCreateDelaySenTree() {
+        if (m_delaySensesp) return m_delaySensesp;
+        FileLine* const flp = m_scopeTopp->fileline();
+        auto* const awaitingCurrentTimep
+            = new AstCMethodHard{flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::READ},
+                                 "awaitingCurrentTime"};
+        awaitingCurrentTimep->dtypeSetBit();
+        m_delaySensesp
+            = new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}};
+        m_netlistp->topScopep()->addSenTreesp(m_delaySensesp);
+        return m_delaySensesp;
+    }
+    // Creates the global dynamic trigger scheduler variable
+    AstVarScope* getCreateDynamicTriggerScheduler() {
+        if (m_dynamicSchedp) return m_dynamicSchedp;
+        auto* const dynSchedDtp
+            = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::DYNAMIC_TRIGGER_SCHEDULER,
+                                VSigning::UNSIGNED};
+        m_netlistp->typeTablep()->addTypesp(dynSchedDtp);
+        m_dynamicSchedp = m_scopeTopp->createTemp("__VdynSched", dynSchedDtp);
+        return m_dynamicSchedp;
+    }
+    // Creates the dynamic trigger sentree
+    AstSenTree* getCreateDynamicTriggerSenTree() {
+        if (m_dynamicSensesp) return m_dynamicSensesp;
+        FileLine* const flp = m_scopeTopp->fileline();
+        auto* const awaitingCurrentTimep = new AstCMethodHard{
+            flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::READ},
+            "evaluate"};
+        awaitingCurrentTimep->dtypeSetBit();
+        m_dynamicSensesp
+            = new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}};
+        m_netlistp->topScopep()->addSenTreesp(m_dynamicSensesp);
+        return m_dynamicSensesp;
+    }
+    // Returns true if we are under a class or the given tree has any references to locals. These
+    // are cases where static, globally-evaluated triggers are not suitable.
+    bool needDynamicTrigger(AstNode* const nodep) const {
+        return m_classp || nodep->exists([](const AstNodeVarRef* const refp) {
+            return refp->varp()->isFuncLocal();
+        });
+    }
+    // Returns true if the given trigger expression needs a destructive post update after trigger
+    // evaluation. Currently this only applies to named events.
+    bool destructivePostUpdate(AstNode* const exprp) const {
+        return exprp->exists([](const AstNodeVarRef* const refp) {
+            AstBasicDType* const dtypep = refp->dtypep()->basicp();
+            return dtypep && dtypep->isEvent();
+        });
+    }
+    // Creates a trigger scheduler variable
+    AstVarScope* getCreateTriggerSchedulerp(AstSenTree* const sensesp) {
+        if (!sensesp->user1p()) {
+            if (!m_trigSchedDtp) {
+                m_trigSchedDtp
+                    = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::TRIGGER_SCHEDULER,
+                                        VSigning::UNSIGNED};
+                m_netlistp->typeTablep()->addTypesp(m_trigSchedDtp);
+            }
+            AstVarScope* const trigSchedp
+                = m_scopeTopp->createTemp(m_trigSchedNames.get(sensesp), m_trigSchedDtp);
+            sensesp->user1p(trigSchedp);
+        }
+        return VN_AS(sensesp->user1p(), VarScope);
+    }
+    // Creates a string describing the sentree
+    AstText* createEventDescription(AstSenTree* const sensesp) const {
+        if (!sensesp->user2p()) {
+            std::stringstream ss;
+            ss << '"';
+            V3EmitV::verilogForTree(sensesp, ss);
+            ss << '"';
+            auto* const commentp = new AstText{sensesp->fileline(), ss.str()};
+            sensesp->user2p(commentp);
+            return commentp;
+        }
+        return VN_AS(sensesp->user2p(), Text)->cloneTree(false);
+    }
+    // Adds debug info to a hardcoded method call
+    void addDebugInfo(AstCMethodHard* const methodp) const {
+        if (v3Global.opt.protectIds()) return;
+        FileLine* const flp = methodp->fileline();
+        methodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
+        methodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
+    }
+    // Adds debug info to a trigSched.trigger() call
+    void addEventDebugInfo(AstCMethodHard* const methodp, AstSenTree* const sensesp) const {
+        if (v3Global.opt.protectIds()) return;
+        methodp->addPinsp(createEventDescription(sensesp));
+        addDebugInfo(methodp);
+    }
+    // Creates the fork handle type and returns it
+    AstBasicDType* getCreateForkSyncDTypep() {
+        if (m_forkDtp) return m_forkDtp;
+        m_forkDtp = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::FORK_SYNC,
+                                      VSigning::UNSIGNED};
+        m_netlistp->typeTablep()->addTypesp(m_forkDtp);
+        return m_forkDtp;
+    }
+    // Create a temp variable and optionally put it before the specified node (mark local if so)
+    AstVarScope* createTemp(FileLine* const flp, const std::string& name,
+                            AstNodeDType* const dtypep, AstNode* const insertBeforep = nullptr) {
+        AstVar* varp;
+        if (insertBeforep) {
+            varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
+            varp->funcLocal(true);
+            insertBeforep->addHereThisAsNext(varp);
+        } else {
+            varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
+            m_scopep->modp()->addStmtsp(varp);
+        }
+        AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp};
+        m_scopep->addVarsp(vscp);
+        return vscp;
+    }
+    // Add a done() call on the fork sync
+    void addForkDone(AstBegin* const beginp, AstVarScope* const forkVscp) const {
+        FileLine* const flp = beginp->fileline();
+        auto* const donep = new AstCMethodHard{
+            beginp->fileline(), new AstVarRef{flp, forkVscp, VAccess::WRITE}, "done"};
+        donep->dtypeSetVoid();
+        donep->statement(true);
+        addDebugInfo(donep);
+        beginp->addStmtsp(donep);
+    }
+    // Handle the 'join' part of a fork..join
+    void makeForkJoin(AstFork* const forkp) {
+        // Create a fork sync var
+        FileLine* const flp = forkp->fileline();
+        // If we're in a function, insert the sync var directly before the fork
+        AstNode* const insertBeforep = VN_IS(m_procp, CFunc) ? forkp : nullptr;
+        AstVarScope* forkVscp
+            = createTemp(flp, forkp->name() + "__sync", getCreateForkSyncDTypep(), insertBeforep);
+        unsigned joinCount = 0;  // Needed for join counter
+        // Add a .done() to each begin
+        for (AstNode* beginp = forkp->stmtsp(); beginp; beginp = beginp->nextp()) {
+            addForkDone(VN_AS(beginp, Begin), forkVscp);
+            joinCount++;
+        }
+        if (forkp->joinType().joinAny()) joinCount = 1;
+        // Set the join counter
+        auto* const initp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE},
+                                               "init", new AstConst{flp, joinCount}};
+        initp->dtypeSetVoid();
+        initp->statement(true);
+        forkp->addHereThisAsNext(initp);
+        // Await the join at the end
+        auto* const joinp
+            = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"};
+        joinp->dtypeSetVoid();
+        addDebugInfo(joinp);
+        auto* const awaitp = new AstCAwait{flp, joinp};
+        awaitp->statement(true);
+        forkp->addNextHere(awaitp);
+    }
+
+    // VISITORS
+    void visit(AstNodeModule* nodep) override {
+        UASSERT(!m_classp, "Module or class under class");
+        VL_RESTORER(m_classp);
+        m_classp = VN_CAST(nodep, Class);
+        VL_RESTORER(m_timescaleFactor);
+        m_timescaleFactor = calculateTimescaleFactor(nodep->timeunit());
+        iterateChildren(nodep);
+    }
+    void visit(AstScope* nodep) override {
+        VL_RESTORER(m_scopep);
+        m_scopep = nodep;
+        iterateChildren(nodep);
+    }
+    void visit(AstActive* nodep) override {
+        m_activep = nodep;
+        iterateChildren(nodep);
+        m_activep = nullptr;
+    }
+    void visit(AstNodeProcedure* nodep) override {
+        VL_RESTORER(m_procp);
+        m_procp = nodep;
+        iterateChildren(nodep);
+        if (nodep->user2()) nodep->setSuspendable();
+    }
+    void visit(AstAlways* nodep) override {
+        visit(static_cast(nodep));
+        if (nodep->isSuspendable() && !nodep->user1SetOnce()) {
+            FileLine* const flp = nodep->fileline();
+            AstSenTree* const sensesp = m_activep->sensesp();
+            if (sensesp->hasClocked()) {
+                AstNode* bodysp = nodep->stmtsp()->unlinkFrBackWithNext();
+                auto* const controlp = new AstEventControl{flp, sensesp->cloneTree(false), bodysp};
+                nodep->addStmtsp(controlp);
+                iterate(controlp);
+            }
+            // Note: The 'while (true)' outer loop will be added in V3Sched
+            auto* const activep = new AstActive{
+                flp, "", new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}};
+            activep->sensesStorep(activep->sensesp());
+            activep->addStmtsp(nodep->unlinkFrBack());
+            m_activep->addNextHere(activep);
+        }
+    }
+    void visit(AstCFunc* nodep) override {
+        VL_RESTORER(m_procp);
+        m_procp = nodep;
+        iterateChildren(nodep);
+        DependencyVertex* const vxp = getDependencyVertex(nodep);
+        if (m_classp && nodep->isVirtual()
+            && !nodep->user1SetOnce()) {  // If virtual (only visit once)
+            // Go over overridden functions
+            m_classp->repairCache();
+            for (auto* cextp = m_classp->extendsp(); cextp;
+                 cextp = VN_AS(cextp->nextp(), ClassExtends)) {
+                if (auto* const overriddenp
+                    = VN_CAST(cextp->classp()->findMember(nodep->name()), CFunc)) {
+                    if (overriddenp->user2()) {  // If suspendable
+                        if (!nodep->user2()) {
+                            // It should be a coroutine but it has no awaits. Add a co_return at
+                            // the end (either that or a co_await is required in a coroutine)
+                            nodep->addStmtsp(new AstCStmt{nodep->fileline(), "co_return;\n"});
+                        }
+                        nodep->user2(true);
+                        // If it's suspendable already, no need to add it as our dependency or
+                        // self to its dependencies
+                    } else {
+                        DependencyVertex* const overriddenVxp = getDependencyVertex(overriddenp);
+                        new V3GraphEdge{&m_depGraph, vxp, overriddenVxp, 1};
+                        new V3GraphEdge{&m_depGraph, overriddenVxp, vxp, 1};
+                    }
+                }
+            }
+        }
+        if (nodep->user2() && !nodep->isCoroutine()) {  // If first marked as suspendable
+            nodep->rtnType("VlCoroutine");
+            // If in a class, create a shared pointer to 'this'
+            if (m_classp) nodep->addInitsp(new AstCStmt{nodep->fileline(), "VL_KEEP_THIS;\n"});
+            // Revisit dependent nodes if needed
+            for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
+                auto* const depVxp = static_cast(edgep->fromp());
+                AstNode* const depp = depVxp->nodep();
+                if (!depp->user2()) {  // If dependent not suspendable
+                    depp->user2(true);
+                    if (auto* const funcp = VN_CAST(depp, CFunc)) {
+                        // It's a coroutine but has no awaits (a class method that overrides/is
+                        // overridden by a suspendable, but doesn't have any awaits itself). Add a
+                        // co_return at the end (either that or a co_await is required in a
+                        // coroutine)
+                        funcp->addStmtsp(new AstCStmt{funcp->fileline(), "co_return;\n"});
+                    }
+                }
+                iterate(depp);
+            }
+        }
+    }
+    void visit(AstNodeCCall* nodep) override {
+        if (nodep->funcp()->user2()) {  // If suspendable
+            VNRelinker relinker;
+            nodep->unlinkFrBack(&relinker);
+            relinker.relink(new AstCAwait{nodep->fileline(), nodep});
+        } else {
+            // Add our process/func as the CFunc's dependency as we might have to put an await here
+            DependencyVertex* const procVxp = getDependencyVertex(m_procp);
+            DependencyVertex* const funcVxp = getDependencyVertex(nodep->funcp());
+            new V3GraphEdge{&m_depGraph, procVxp, funcVxp, 1};
+        }
+        iterateChildren(nodep);
+    }
+    void visit(AstCAwait* nodep) override {
+        v3Global.setUsesTiming();
+        m_procp->user2(true);
+    }
+    void visit(AstDelay* nodep) override {
+        FileLine* const flp = nodep->fileline();
+        AstNode* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
+        auto* const constp = VN_CAST(valuep, Const);
+        if (constp && constp->isZero()) {
+            nodep->v3warn(ZERODLY, "Unsupported: #0 delays do not schedule process resumption in "
+                                   "the Inactive region");
+        } else {
+            // Scale the delay
+            if (valuep->dtypep()->isDouble()) {
+                valuep = new AstRToIRoundS{
+                    flp,
+                    new AstMulD{flp, valuep,
+                                new AstConst{flp, AstConst::RealDouble{}, m_timescaleFactor}}};
+            } else {
+                valuep = new AstMul{flp, valuep,
+                                    new AstConst{flp, AstConst::Unsized64(),
+                                                 static_cast(m_timescaleFactor)}};
+            }
+        }
+        // Replace self with a 'co_await dlySched.delay()'
+        auto* const delayMethodp = new AstCMethodHard{
+            flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, "delay", valuep};
+        delayMethodp->dtypeSetVoid();
+        addDebugInfo(delayMethodp);
+        // Create the co_await
+        auto* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()};
+        awaitp->statement(true);
+        // Relink child statements after the co_await
+        if (nodep->stmtsp()) {
+            AstNode::addNext(awaitp, nodep->stmtsp()->unlinkFrBackWithNext());
+        }
+        nodep->replaceWith(awaitp);
+        VL_DO_DANGLING(nodep->deleteTree(), nodep);
+    }
+    void visit(AstEventControl* nodep) override {
+        // Do not allow waiting on local named events, as they get enqueued for clearing, but can
+        // go out of scope before that happens
+        if (nodep->sensesp()->exists([](const AstNodeVarRef* refp) {
+                AstBasicDType* const dtypep = refp->dtypep()->skipRefp()->basicp();
+                return dtypep && dtypep->isEvent() && refp->varp()->isFuncLocal();
+            })) {
+            nodep->v3warn(E_UNSUPPORTED, "Unsupported: waiting on local event variables");
+        }
+        FileLine* const flp = nodep->fileline();
+        // Relink child statements after the event control
+        if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());
+        if (needDynamicTrigger(nodep->sensesp())) {
+            // Create the trigger variable and init it with 0
+            AstVarScope* const trigvscp
+                = createTemp(flp, m_dynTrigNames.get(nodep), nodep->findBitDType(), nodep);
+            auto* const initp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
+                                              new AstConst{flp, AstConst::BitFalse{}}};
+            nodep->addHereThisAsNext(initp);
+            // Await the eval step with the dynamic trigger scheduler. First, create the method
+            // call
+            auto* const evalMethodp = new AstCMethodHard{
+                flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::WRITE},
+                "evaluation"};
+            evalMethodp->dtypeSetVoid();
+            auto* const sensesp = nodep->sensesp();
+            addEventDebugInfo(evalMethodp, sensesp);
+            // Create the co_await
+            auto* const awaitEvalp
+                = new AstCAwait{flp, evalMethodp, getCreateDynamicTriggerSenTree()};
+            awaitEvalp->statement(true);
+            // Construct the sen expression for this sentree
+            SenExprBuilder senExprBuilder{m_scopep};
+            auto* const assignp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
+                                                senExprBuilder.build(sensesp).first};
+            // Put all the locals and inits before the trigger eval loop
+            for (AstVar* const varp : senExprBuilder.getAndClearLocals()) {
+                nodep->addHereThisAsNext(varp);
+            }
+            for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearInits()) {
+                nodep->addHereThisAsNext(stmtp);
+            }
+            // Create the trigger eval loop, which will await the evaluation step and check the
+            // trigger
+            auto* const loopp = new AstWhile{
+                flp, new AstLogNot{flp, new AstVarRef{flp, trigvscp, VAccess::READ}}, awaitEvalp};
+            // Put pre updates before the trigger check and assignment
+            for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearPreUpdates()) {
+                loopp->addStmtsp(stmtp);
+            }
+            // Then the trigger check and assignment
+            loopp->addStmtsp(assignp);
+            // If the post update is destructive (e.g. event vars are cleared), create an await for
+            // the post update step
+            if (destructivePostUpdate(sensesp)) {
+                auto* const awaitPostUpdatep = awaitEvalp->cloneTree(false);
+                VN_AS(awaitPostUpdatep->exprp(), CMethodHard)->name("postUpdate");
+                loopp->addStmtsp(awaitPostUpdatep);
+            }
+            // Put the post updates at the end of the loop
+            for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearPostUpdates()) {
+                loopp->addStmtsp(stmtp);
+            }
+            // Finally, await the resumption step in 'act'
+            auto* const awaitResumep = awaitEvalp->cloneTree(false);
+            VN_AS(awaitResumep->exprp(), CMethodHard)->name("resumption");
+            AstNode::addNext(loopp, awaitResumep);
+            // Replace the event control with the loop
+            nodep->replaceWith(loopp);
+        } else {
+            auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
+            nodep->sensesp()->unlinkFrBack()->deleteTree();
+            // Get this sentree's trigger scheduler
+            FileLine* const flp = nodep->fileline();
+            // Replace self with a 'co_await trigSched.trigger()'
+            auto* const triggerMethodp = new AstCMethodHard{
+                flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
+                "trigger"};
+            triggerMethodp->dtypeSetVoid();
+            addEventDebugInfo(triggerMethodp, sensesp);
+            // Create the co_await
+            auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
+            awaitp->statement(true);
+            nodep->replaceWith(awaitp);
+        }
+        VL_DO_DANGLING(nodep->deleteTree(), nodep);
+    }
+    void visit(AstNodeAssign* nodep) override {
+        // Only process once to avoid infinite loops (due to the net delay)
+        if (nodep->user1SetOnce()) return;
+        AstNode* const controlp = factorOutTimingControl(nodep);
+        if (!controlp) return;
+        // Handle the intra assignment timing control
+        FileLine* const flp = nodep->fileline();
+        if (VN_IS(nodep, AssignDly)) {
+            // If it's an NBA with an intra assignment delay, put it in a fork
+            auto* const forkp = new AstFork{flp, "", nullptr};
+            forkp->joinType(VJoinType::JOIN_NONE);
+            controlp->replaceWith(forkp);
+            forkp->addStmtsp(controlp);
+        }
+        // Insert new vars before the timing control if we're in a function; in a process we can't
+        // do that. These intra-assignment vars will later be passed to forked processes by value.
+        AstNode* const insertBeforep = VN_IS(m_procp, CFunc) ? controlp : nullptr;
+        // Function for replacing values with intermediate variables
+        const auto replaceWithIntermediate = [&](AstNode* const valuep, const std::string& name) {
+            AstVarScope* const newvscp = createTemp(flp, name, valuep->dtypep(), insertBeforep);
+            valuep->replaceWith(new AstVarRef{flp, newvscp, VAccess::READ});
+            controlp->addHereThisAsNext(
+                new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, valuep});
+        };
+        // Create the intermediate select vars. Note: because 'foreach' proceeds in
+        // pre-order, and we replace indices in selects with variables, we cannot
+        // reach another select under the index position. This is exactly what
+        // we want as only the top level selects are LValues. As an example,
+        // this transforms 'x[a[i]][b[j]] = y'
+        // into 't1 = a[i]; t0 = b[j]; x[t1][t0] = y'.
+        nodep->lhsp()->foreach([&](AstSel* selp) {
+            if (VN_IS(selp->lsbp(), Const)) return;
+            replaceWithIntermediate(selp->lsbp(), m_intraLsbNames.get(nodep));
+            // widthp should be const
+        });
+        nodep->lhsp()->foreach([&](AstNodeSel* selp) {
+            if (VN_IS(selp->bitp(), Const)) return;
+            replaceWithIntermediate(selp->bitp(), m_intraIndexNames.get(nodep));
+        });
+        // Replace the RHS with an intermediate value var
+        replaceWithIntermediate(nodep->rhsp(), m_intraValueNames.get(nodep));
+    }
+    void visit(AstAssignW* nodep) override {
+        AstDelay* const netDelayp = getLhsNetDelay(nodep);
+        if (!netDelayp && !nodep->timingControlp()) return;
+        // This assignment will be converted to an always. In some cases this may generate an
+        // UNOPTFLAT, e.g.: assign #1 clk = ~clk. We create a temp var for the LHS of this
+        // assign, to disable the UNOPTFLAT warning for it.
+        // TODO: Find a way to do this without introducing this var. Perhaps make
+        // V3SchedAcyclic recognize awaits and prevent it from treating this kind of logic as
+        // cyclic
+        AstNode* const lhsp = nodep->lhsp()->unlinkFrBack();
+        std::string varname;
+        if (auto* const refp = VN_CAST(lhsp, VarRef)) {
+            varname = m_contAssignVarNames.get(refp->name());
+        } else {
+            varname = m_contAssignVarNames.get(lhsp);
+        }
+        auto* const tempvscp = m_scopep->createTemp(varname, lhsp->dtypep());
+        tempvscp->varp()->delayp(netDelayp);
+        FileLine* const flp = nodep->fileline();
+        flp->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true);
+        tempvscp->fileline(flp);
+        tempvscp->varp()->fileline(flp);
+        // Remap the LHS to the new temp var
+        nodep->lhsp(new AstVarRef{flp, tempvscp, VAccess::WRITE});
+        // Convert it to an always; the new assign with intra delay will be handled by
+        // visit(AstNodeAssign*)
+        AstAlways* const alwaysp = nodep->convertToAlways();
+        visit(alwaysp);
+        // Put the LHS back in the AssignW; put the temp var on the RHS
+        nodep->lhsp(lhsp);
+        nodep->rhsp(new AstVarRef{flp, tempvscp, VAccess::READ});
+        // Put the AssignW right after the always. Different order can produce UNOPTFLAT on the LHS
+        // var
+        alwaysp->addNextHere(nodep);
+    }
+    void visit(AstWait* nodep) override {
+        // Wait on changed events related to the vars in the wait statement
+        FileLine* const flp = nodep->fileline();
+        AstNode* const stmtsp = nodep->stmtsp();
+        if (stmtsp) stmtsp->unlinkFrBackWithNext();
+        AstNode* const condp = V3Const::constifyEdit(nodep->condp()->unlinkFrBack());
+        auto* const constp = VN_CAST(condp, Const);
+        if (constp) {
+            condp->v3warn(WAITCONST, "Wait statement condition is constant");
+            if (constp->isZero()) {
+                // We have to await forever instead of simply returning in case we're deep in a
+                // callstack
+                auto* const awaitp = new AstCAwait{flp, new AstCStmt{flp, "VlForever{}"}};
+                awaitp->statement(true);
+                nodep->replaceWith(awaitp);
+                if (stmtsp) VL_DO_DANGLING(stmtsp->deleteTree(), stmtsp);
+            } else if (stmtsp) {
+                // Just put the statements there
+                nodep->replaceWith(stmtsp);
+            }
+            VL_DO_DANGLING(condp->deleteTree(), condp);
+        } else if (needDynamicTrigger(condp)) {
+            // No point in making a sentree, just use the expression as sensitivity
+            // Put the event control in an if so we only wait if the condition isn't met already
+            auto* const ifp = new AstIf{
+                flp, new AstLogNot{flp, condp},
+                new AstEventControl{flp,
+                                    new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE,
+                                                                       condp->cloneTree(false)}},
+                                    nullptr}};
+            if (stmtsp) AstNode::addNext(ifp, stmtsp);
+            nodep->replaceWith(ifp);
+        } else {
+            AstSenItem* const senItemsp = varRefpsToSenItemsp(condp);
+            UASSERT_OBJ(senItemsp, nodep, "No varrefs in wait statement condition");
+            // Put the event control in a while loop with the wait expression as condition
+            auto* const loopp
+                = new AstWhile{flp, new AstLogNot{flp, condp},
+                               new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}};
+            if (stmtsp) AstNode::addNext(loopp, stmtsp);
+            nodep->replaceWith(loopp);
+        }
+        VL_DO_DANGLING(nodep->deleteTree(), nodep);
+    }
+    void visit(AstFork* nodep) override {
+        if (nodep->user1SetOnce()) return;
+        // Create a unique name for this fork
+        nodep->name(m_forkNames.get(nodep));
+        unsigned idx = 0;  // Index for naming begins
+        AstNode* stmtp = nodep->stmtsp();
+        // Put each statement in a begin
+        while (stmtp) {
+            if (!VN_IS(stmtp, Begin)) {
+                auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr};
+                stmtp->replaceWith(beginp);
+                beginp->addStmtsp(stmtp);
+                stmtp = beginp;
+            }
+            auto* const beginp = VN_AS(stmtp, Begin);
+            stmtp = beginp->nextp();
+            VL_RESTORER(m_procp);
+            m_procp = beginp;
+            iterate(beginp);
+            // Even if we do not find any awaits, we cannot simply inline the process here, as new
+            // awaits could be added later.
+            // Name the begin (later the name will be used for a new function)
+            beginp->name(nodep->name() + "__" + cvtToStr(idx++));
+        }
+        if (!nodep->joinType().joinNone()) makeForkJoin(nodep);
+    }
+
+    //--------------------
+    void visit(AstNodeMath*) override {}  // Accelerate
+    void visit(AstVar*) override {}
+    void visit(AstNode* nodep) override { iterateChildren(nodep); }
+
+public:
+    // CONSTRUCTORS
+    explicit TimingVisitor(AstNetlist* nodep)
+        : m_netlistp{nodep} {
+        iterate(nodep);
+        if (dumpGraph() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps");
+    }
+    ~TimingVisitor() override = default;
+};
+
+//######################################################################
+// Timing class functions
+
+void V3Timing::timingAll(AstNetlist* nodep) {
+    UINFO(2, __FUNCTION__ << ": " << endl);
+    TimingVisitor{nodep};
+    V3Global::dumpCheckGlobalTree("timing", 0, dumpTree() >= 3);
+}
diff --git a/src/V3GenClk.h b/src/V3Timing.h
similarity index 80%
rename from src/V3GenClk.h
rename to src/V3Timing.h
index df20f0c82..2114f1bbb 100644
--- a/src/V3GenClk.h
+++ b/src/V3Timing.h
@@ -1,6 +1,6 @@
 // -*- mode: C++; c-file-style: "cc-mode" -*-
 //*************************************************************************
-// DESCRIPTION: Verilator: Generated Clock Repairs
+// DESCRIPTION: Verilator: Prepare AST for features features
 //
 // Code available from: https://verilator.org
 //
@@ -14,19 +14,19 @@
 //
 //*************************************************************************
 
-#ifndef VERILATOR_V3GENCLK_H_
-#define VERILATOR_V3GENCLK_H_
+#ifndef _V3TIMING_H_
+#define _V3TIMING_H_
 
 #include "config_build.h"
 #include "verilatedos.h"
 
-class AstNetlist;
+#include "V3Ast.h"
 
 //============================================================================
 
-class V3GenClk final {
+class V3Timing final {
 public:
-    static void genClkAll(AstNetlist* nodep);
+    static void timingAll(AstNetlist* nodep);
 };
 
 #endif  // Guard
diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp
index b19b1c249..89703a67f 100644
--- a/src/V3Trace.cpp
+++ b/src/V3Trace.cpp
@@ -432,6 +432,12 @@ private:
         if (AstCCall* const callp = VN_CAST(insertp, CCall)) {
             callp->addNextHere(setterp);
         } else if (AstCFunc* const funcp = VN_CAST(insertp, CFunc)) {
+            // If there are awaits, insert the setter after each await
+            if (funcp->isCoroutine() && funcp->stmtsp()) {
+                funcp->stmtsp()->foreachAndNext([&](AstCAwait* awaitp) {
+                    if (awaitp->nextp()) awaitp->addNextHere(setterp->cloneTree(false));
+                });
+            }
             funcp->addStmtsp(setterp);
         } else {
             insertp->v3fatalSrc("Bad trace activity vertex");
@@ -447,7 +453,7 @@ private:
         // read-modify-write on the C type), and the speed of the tracing code
         // is the same on largish designs.
         FileLine* const flp = m_topScopep->fileline();
-        AstNodeDType* const newScalarDtp = new AstBasicDType(flp, VFlagLogicPacked(), 1);
+        AstNodeDType* const newScalarDtp = new AstBasicDType{flp, VFlagBitPacked{}, 1};
         v3Global.rootp()->typeTablep()->addTypesp(newScalarDtp);
         AstRange* const newArange
             = new AstRange{flp, VNumRange{static_cast(m_activityNumber) - 1, 0}};
@@ -840,9 +846,11 @@ private:
         V3GraphVertex* const funcVtxp = getCFuncVertexp(nodep);
         if (!m_finding) {  // If public, we need a unique activity code to allow for sets
                            // directly in this func
-            if (nodep->funcPublic() || nodep->dpiExportImpl()
-                || nodep == v3Global.rootp()->evalp()) {
-                V3GraphVertex* const activityVtxp = getActivityVertexp(nodep, nodep->slow());
+            if (nodep->funcPublic() || nodep->dpiExportImpl() || nodep == v3Global.rootp()->evalp()
+                || nodep->isCoroutine()) {
+                // Cannot treat a coroutine as slow, it may be resumed later
+                const bool slow = nodep->slow() && !nodep->isCoroutine();
+                V3GraphVertex* const activityVtxp = getActivityVertexp(nodep, slow);
                 new V3GraphEdge(&m_graph, activityVtxp, funcVtxp, 1);
             }
         }
diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp
index 55dddaacc..7e35067eb 100644
--- a/src/V3Tristate.cpp
+++ b/src/V3Tristate.cpp
@@ -493,6 +493,47 @@ class TristateVisitor final : public TristateBaseVisitor {
         }
         return VN_AS(invarp->user1p(), Var);
     }
+    AstConst* getNonZConstp(AstConst* const constp) {
+        FileLine* const fl = constp->fileline();
+        V3Number numz{constp, constp->width()};
+        numz.opBitsZ(constp->num());  // Z->1, else 0
+        V3Number numz0{constp, constp->width()};
+        numz0.opNot(numz);  // Z->0, else 1
+        return new AstConst{fl, numz0};
+    }
+    AstNode* getEnExprBasedOnOriginalp(AstNode* const nodep) {
+        if (AstVarRef* const varrefp = VN_CAST(nodep, VarRef)) {
+            return new AstVarRef{varrefp->fileline(), getCreateEnVarp(varrefp->varp()),
+                                 VAccess::READ};
+        } else if (AstConst* const constp = VN_CAST(nodep, Const)) {
+            return getNonZConstp(constp);
+        } else if (AstExtend* const extendp = VN_CAST(nodep, Extend)) {
+            // Extend inserts 0 at the beginning. 0 in __en variable means that this bit equals z,
+            // so in order to preserve the value of the original AstExtend node we should insert 1
+            // instead of 0. To extend __en expression we have to negate its lhsp() and then negate
+            // whole extend.
+
+            // Unlink lhsp before copying to save unnecessary copy of lhsp
+            AstNode* const lhsp = extendp->lhsp()->unlinkFrBack();
+            AstExtend* const enExtendp = extendp->cloneTree(false);
+            extendp->lhsp(lhsp);
+            AstNode* const enLhsp = getEnExprBasedOnOriginalp(lhsp);
+            enExtendp->lhsp(new AstNot{enLhsp->fileline(), enLhsp});
+            return new AstNot{enExtendp->fileline(), enExtendp};
+        } else if (AstSel* const selp = VN_CAST(nodep, Sel)) {
+            AstNode* const fromp = selp->fromp()->unlinkFrBack();
+            AstSel* const enSelp = selp->cloneTree(false);
+            selp->fromp(fromp);
+            AstNode* const enFromp = getEnExprBasedOnOriginalp(fromp);
+            enSelp->fromp(enFromp);
+            return enSelp;
+        } else {
+            nodep->v3warn(E_UNSUPPORTED,
+                          "Unsupported tristate construct: " << nodep->prettyTypeName()
+                                                             << " in function " << __func__);
+            return nullptr;
+        }
+    }
     AstVar* getCreateOutVarp(AstVar* invarp) {
         // Return the master __out for the specified input variable
         if (!invarp->user4p()) {
@@ -959,14 +1000,10 @@ class TristateVisitor final : public TristateBaseVisitor {
             } else if (m_tgraph.isTristate(nodep)) {
                 m_tgraph.didProcess(nodep);
                 FileLine* const fl = nodep->fileline();
-                V3Number numz(nodep, nodep->width());
-                numz.opBitsZ(nodep->num());  // Z->1, else 0
-                V3Number numz0(nodep, nodep->width());
-                numz0.opNot(numz);  // Z->0, else 1
-                V3Number num1(nodep, nodep->width());
-                num1.opAnd(nodep->num(), numz0);  // 01X->01X, Z->0
-                AstConst* const newconstp = new AstConst(fl, num1);
-                AstConst* const enp = new AstConst(fl, numz0);
+                AstConst* const enp = getNonZConstp(nodep);
+                V3Number num1{nodep, nodep->width()};
+                num1.opAnd(nodep->num(), enp->num());  // 01X->01X, Z->0
+                AstConst* const newconstp = new AstConst{fl, num1};
                 nodep->replaceWith(newconstp);
                 VL_DO_DANGLING(pushDeletep(nodep), nodep);
                 newconstp->user1p(enp);  // Propagate up constant with non-Z bits as 1
@@ -1263,23 +1300,27 @@ class TristateVisitor final : public TristateBaseVisitor {
             UINFO(9, dbgState() << nodep << endl);
             // Constification always moves const to LHS
             AstConst* const constp = VN_CAST(nodep->lhsp(), Const);
-            AstVarRef* const varrefp = VN_CAST(nodep->rhsp(), VarRef);  // Input variable
-            if (constp && constp->user1p() && varrefp) {
+            if (constp && constp->user1p()) {
                 // 3'b1z0 -> ((3'b101 == in__en) && (3'b100 == in))
-                varrefp->unlinkFrBack();
+                AstNode* const rhsp = nodep->rhsp();
+                rhsp->unlinkFrBack();
                 FileLine* const fl = nodep->fileline();
+                AstNode* enRhsp;
+                if (rhsp->user1p()) {
+                    enRhsp = rhsp->user1p();
+                    rhsp->user1p(nullptr);
+                } else {
+                    enRhsp = getEnExprBasedOnOriginalp(rhsp);
+                }
                 const V3Number oneIfEn
                     = VN_AS(constp->user1p(), Const)
                           ->num();  // visit(AstConst) already split into en/ones
                 const V3Number& oneIfEnOne = constp->num();
-                AstVar* const envarp = getCreateEnVarp(varrefp->varp());
                 AstNode* newp
-                    = new AstLogAnd(fl,
-                                    new AstEq(fl, new AstConst(fl, oneIfEn),
-                                              new AstVarRef(fl, envarp, VAccess::READ)),
+                    = new AstLogAnd{fl, new AstEq{fl, new AstConst{fl, oneIfEn}, enRhsp},
                                     // Keep the caseeq if there are X's present
-                                    new AstEqCase(fl, new AstConst(fl, oneIfEnOne), varrefp));
-                if (neq) newp = new AstLogNot(fl, newp);
+                                    new AstEqCase{fl, new AstConst{fl, oneIfEnOne}, rhsp}};
+                if (neq) newp = new AstLogNot{fl, newp};
                 UINFO(9, "       newceq " << newp << endl);
                 if (debug() >= 9) nodep->dumpTree(cout, "-caseeq-old: ");
                 if (debug() >= 9) newp->dumpTree(cout, "-caseeq-new: ");
diff --git a/src/V3Undriven.cpp b/src/V3Undriven.cpp
index 716005892..c2a757a9b 100644
--- a/src/V3Undriven.cpp
+++ b/src/V3Undriven.cpp
@@ -151,7 +151,20 @@ public:
     void reportViolations() {
         // Combine bits into overall state
         AstVar* const nodep = m_varp;
-        {
+
+        if (nodep->isGenVar()) {  // Genvar
+            if (!nodep->isIfaceRef() && !nodep->isUsedParam() && !unusedMatch(nodep)) {
+                nodep->v3warn(UNUSEDGENVAR, "Genvar is not used: " << nodep->prettyNameQ());
+                nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDGENVAR,
+                                                 true);  // Warn only once
+            }
+        } else if (nodep->isParam()) {  // Parameter
+            if (!nodep->isIfaceRef() && !nodep->isUsedParam() && !unusedMatch(nodep)) {
+                nodep->v3warn(UNUSEDPARAM, "Parameter is not used: " << nodep->prettyNameQ());
+                nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDPARAM,
+                                                 true);  // Warn only once
+            }
+        } else {  // Signal
             bool allU = true;
             bool allD = true;
             bool anyU = m_wholeFlags[FLAG_USED];
@@ -170,13 +183,8 @@ public:
                 anyDnotU |= !used && driv;
                 anynotDU |= !used && !driv;
             }
-            if ((nodep->isGenVar() || nodep->isParam()) && nodep->isUsedParam())
-                allD = allU = true;
             if (allU) m_wholeFlags[FLAG_USED] = true;
             if (allD) m_wholeFlags[FLAG_DRIVEN] = true;
-            const char* const what = nodep->isParam()    ? "parameter"
-                                     : nodep->isGenVar() ? "genvar"
-                                                         : "signal";
             // Test results
             if (nodep->isIfaceRef()) {
                 // For interface top level we don't do any tracking
@@ -188,43 +196,42 @@ public:
                 // UNDRIVEN is considered more serious - as is more likely a bug,
                 // thus undriven+unused bits get UNUSED warnings, as they're not as buggy.
                 if (!unusedMatch(nodep)) {
-                    nodep->v3warn(UNUSED, ucfirst(what) << " is not driven, nor used: "
-                                                        << nodep->prettyNameQ());
-                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true);  // Warn only once
+                    nodep->v3warn(UNUSEDSIGNAL,
+                                  "Signal is not driven, nor used: " << nodep->prettyNameQ());
+                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL,
+                                                     true);  // Warn only once
                 }
             } else if (allD && !anyU) {
                 if (!unusedMatch(nodep)) {
-                    nodep->v3warn(UNUSED, ucfirst(what)
-                                              << " is not used: " << nodep->prettyNameQ());
-                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true);  // Warn only once
+                    nodep->v3warn(UNUSEDSIGNAL, "Signal is not used: " << nodep->prettyNameQ());
+                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL,
+                                                     true);  // Warn only once
                 }
             } else if (!anyD && allU) {
-                nodep->v3warn(UNDRIVEN, ucfirst(what)
-                                            << " is not driven: " << nodep->prettyNameQ());
+                nodep->v3warn(UNDRIVEN, "Signal is not driven: " << nodep->prettyNameQ());
                 nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true);  // Warn only once
             } else {
                 // Bits have different dispositions
                 bool setU = false;
                 bool setD = false;
                 if (anynotDU && !unusedMatch(nodep)) {
-                    nodep->v3warn(UNUSED, "Bits of " << what << " are not driven, nor used: "
-                                                     << nodep->prettyNameQ() << bitNames(BN_BOTH));
+                    nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not driven, nor used: "
+                                                    << nodep->prettyNameQ() << bitNames(BN_BOTH));
                     setU = true;
                 }
                 if (anyDnotU && !unusedMatch(nodep)) {
-                    nodep->v3warn(UNUSED, "Bits of " << what
-                                                     << " are not used: " << nodep->prettyNameQ()
-                                                     << bitNames(BN_UNUSED));
+                    nodep->v3warn(UNUSEDSIGNAL,
+                                  "Bits of signal are not used: " << nodep->prettyNameQ()
+                                                                  << bitNames(BN_UNUSED));
                     setU = true;
                 }
                 if (anyUnotD) {
-                    nodep->v3warn(UNDRIVEN,
-                                  "Bits of " << what << " are not driven: " << nodep->prettyNameQ()
-                                             << bitNames(BN_UNDRIVEN));
+                    nodep->v3warn(UNDRIVEN, "Bits of signal are not driven: "
+                                                << nodep->prettyNameQ() << bitNames(BN_UNDRIVEN));
                     setD = true;
                 }
                 if (setU) {  // Warn only once
-                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true);
+                    nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true);
                 }
                 if (setD) {  // Warn only once
                     nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true);
diff --git a/src/V3UniqueNames.h b/src/V3UniqueNames.h
index 34d83b8e1..1fe8439e9 100644
--- a/src/V3UniqueNames.h
+++ b/src/V3UniqueNames.h
@@ -33,10 +33,14 @@ class V3UniqueNames final {
     std::unordered_map m_multiplicity;  // Suffix number for given key
 
 public:
-    V3UniqueNames()
-        : m_prefix{""} {}
+    V3UniqueNames() = default;
     explicit V3UniqueNames(const std::string& prefix)
-        : m_prefix{prefix} {}
+        : m_prefix{prefix} {
+        if (!m_prefix.empty()) {
+            UASSERT(VString::startsWith(m_prefix, "__V"), "Prefix must start with '__V'");
+            UASSERT(!VString::endsWith(m_prefix, "_"), "Prefix must not end with '_'");
+        }
+    }
 
     // Return argument, prepended with the prefix if any, then appended with a unique suffix each
     // time we are called with the same argument.
diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp
index 22a10af7d..abd550ea5 100644
--- a/src/V3Unknown.cpp
+++ b/src/V3Unknown.cpp
@@ -59,6 +59,7 @@ private:
     AstNodeModule* m_modp = nullptr;  // Current module
     AstAssignW* m_assignwp = nullptr;  // Current assignment
     AstAssignDly* m_assigndlyp = nullptr;  // Current assignment
+    AstNode* m_timingControlp = nullptr;  // Current assignment's intra timing control
     bool m_constXCvt = false;  // Convert X's
     bool m_allowXUnique = true;  // Allow unique assignments
     VDouble0 m_statUnkVars;  // Statistic tracking
@@ -124,12 +125,14 @@ private:
             m_modp->addStmtsp(varp);
             AstNode* const abovep = prep->backp();  // Grab above point before we replace 'prep'
             prep->replaceWith(new AstVarRef(fl, varp, VAccess::WRITE));
+            if (m_timingControlp) m_timingControlp->unlinkFrBack();
             AstIf* const newp = new AstIf(
                 fl, condp,
-                (needDly ? static_cast(
-                     new AstAssignDly(fl, prep, new AstVarRef(fl, varp, VAccess::READ)))
-                         : static_cast(
-                             new AstAssign(fl, prep, new AstVarRef(fl, varp, VAccess::READ)))));
+                (needDly
+                     ? static_cast(new AstAssignDly{
+                         fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp})
+                     : static_cast(new AstAssign{
+                         fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp})));
             newp->branchPred(VBranchPred::BP_LIKELY);
             newp->isBoundsCheck(true);
             if (debug() >= 9) newp->dumpTree(cout, "     _new: ");
@@ -156,18 +159,29 @@ private:
     }
     void visit(AstAssignDly* nodep) override {
         VL_RESTORER(m_assigndlyp);
+        VL_RESTORER(m_timingControlp);
         {
             m_assigndlyp = nodep;
+            m_timingControlp = nodep->timingControlp();
             VL_DO_DANGLING(iterateChildren(nodep), nodep);  // May delete nodep.
         }
     }
     void visit(AstAssignW* nodep) override {
         VL_RESTORER(m_assignwp);
+        VL_RESTORER(m_timingControlp);
         {
             m_assignwp = nodep;
+            m_timingControlp = nodep->timingControlp();
             VL_DO_DANGLING(iterateChildren(nodep), nodep);  // May delete nodep.
         }
     }
+    void visit(AstNodeAssign* nodep) override {
+        VL_RESTORER(m_timingControlp);
+        {
+            m_timingControlp = nodep->timingControlp();
+            iterateChildren(nodep);
+        }
+    }
     void visit(AstCaseItem* nodep) override {
         VL_RESTORER(m_constXCvt);
         {
diff --git a/src/V3Unroll.cpp b/src/V3Unroll.cpp
index 4629f16a5..e0b96f4ae 100644
--- a/src/V3Unroll.cpp
+++ b/src/V3Unroll.cpp
@@ -438,7 +438,7 @@ private:
         if (m_generate) {  // Ignore for's when expanding genfor's
             iterateChildren(nodep);
         } else {
-            nodep->v3error("V3Begin should have removed standard FORs");
+            nodep->v3fatalSrc("V3Begin should have removed standard FORs");
         }
     }
 
diff --git a/src/V3Width.cpp b/src/V3Width.cpp
index 040b5b2bf..e5a7050fd 100644
--- a/src/V3Width.cpp
+++ b/src/V3Width.cpp
@@ -602,8 +602,20 @@ private:
             VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
             return;
         }
+        if (nodep->fileline()->timingOn()) {
+            if (v3Global.opt.timing().isSetTrue()) {
+                userIterate(nodep->lhsp(), WidthVP{nullptr, BOTH}.p());
+                iterateNull(nodep->stmtsp());
+                return;
+            } else if (v3Global.opt.timing().isSetFalse()) {
+                nodep->v3warn(STMTDLY, "Ignoring delay on this statement due to --no-timing");
+            } else {
+                nodep->v3warn(
+                    E_NEEDTIMINGOPT,
+                    "Use --timing or --no-timing to specify how delays should be handled");
+            }
+        }
         if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack());
-        nodep->v3warn(STMTDLY, "Unsupported: Ignoring delay on this delayed statement.");
         VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
     }
     void visit(AstFork* nodep) override {
@@ -613,19 +625,27 @@ private:
             VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
             return;
         }
-        if (v3Global.opt.bboxUnsup()
+        if (!nodep->fileline()->timingOn()
             // With no statements, begin is identical
             || !nodep->stmtsp()
-            // With one statement, a begin block does as good as a fork/join or join_any
-            || (!nodep->stmtsp()->nextp() && !nodep->joinType().joinNone())) {
+            || (!v3Global.opt.timing().isSetTrue()  // If no --timing
+                && (v3Global.opt.bboxUnsup()
+                    // With one statement and no timing, a begin block does as good as a
+                    // fork/join or join_any
+                    || (!nodep->stmtsp()->nextp() && !nodep->joinType().joinNone())))) {
             AstNode* stmtsp = nullptr;
             if (nodep->stmtsp()) stmtsp = nodep->stmtsp()->unlinkFrBack();
             AstBegin* const newp = new AstBegin{nodep->fileline(), nodep->name(), stmtsp};
             nodep->replaceWith(newp);
             VL_DO_DANGLING(nodep->deleteTree(), nodep);
+        } else if (v3Global.opt.timing().isSetTrue()) {
+            iterateChildren(nodep);
+        } else if (v3Global.opt.timing().isSetFalse()) {
+            nodep->v3warn(E_NOTIMING, "Fork statements require --timing");
+            VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
         } else {
-            nodep->v3warn(E_UNSUPPORTED, "Unsupported: fork statements");
-            // TBD might support only normal join, if so complain about other join flavors
+            nodep->v3warn(E_NEEDTIMINGOPT,
+                          "Use --timing or --no-timing to specify how forks should be handled");
         }
     }
     void visit(AstDisableFork* nodep) override {
@@ -1353,10 +1373,28 @@ private:
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
     void visit(AstEventControl* nodep) override {
-        nodep->v3warn(E_UNSUPPORTED, "Unsupported: event control statement in this location\n"
-                                         << nodep->warnMore()
-                                         << "... Suggest have one event control statement "
-                                         << "per procedure, at the top of the procedure");
+        if (VN_IS(m_ftaskp, Func)) {
+            nodep->v3error("Event controls are not legal in functions. Suggest use a task "
+                           "(IEEE 1800-2017 13.4.4)");
+            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
+            return;
+        }
+        if (nodep->fileline()->timingOn()) {
+            if (v3Global.opt.timing().isSetTrue()) {
+                iterateChildren(nodep);
+                return;
+            } else if (v3Global.opt.timing().isSetFalse()) {
+                nodep->v3warn(E_NOTIMING,
+                              "Event control statement in this location requires --timing\n"
+                                  << nodep->warnMore()
+                                  << "... With --no-timing, suggest have one event control "
+                                  << "statement per procedure, at the top of the procedure");
+            } else {
+                nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how "
+                                               "event controls should be handled");
+            }
+        }
+        if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack());
         VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
     }
     void visit(AstAttrOf* nodep) override {
@@ -2313,7 +2351,7 @@ private:
     void visit(AstInside* nodep) override {
         userIterateAndNext(nodep->exprp(), WidthVP(CONTEXT, PRELIM).p());
         for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
-            nextip = itemp->nextp();  // Prelim may cause the node to get replaced
+            nextip = itemp->nextp();  // iterate may cause the node to get replaced
             VL_DO_DANGLING(userIterate(itemp, WidthVP(CONTEXT, PRELIM).p()), itemp);
         }
         // Take width as maximum across all items
@@ -2328,7 +2366,8 @@ private:
             = nodep->findLogicDType(width, mwidth, nodep->exprp()->dtypep()->numeric());
         iterateCheck(nodep, "Inside expression", nodep->exprp(), CONTEXT, FINAL, subDTypep,
                      EXTEND_EXP);
-        for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) {
+        for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
+            nextip = itemp->nextp();  // iterate may cause the node to get replaced
             iterateCheck(nodep, "Inside Item", itemp, CONTEXT, FINAL, subDTypep, EXTEND_EXP);
         }
         nodep->dtypeSetBit();
@@ -2427,6 +2466,10 @@ private:
         userIterateChildren(nodep, nullptr);  // First size all members
         nodep->repairCache();
     }
+    void visit(AstThisRef* nodep) override {
+        if (nodep->didWidthAndSet()) return;
+        nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->childDTypep()));
+    }
     void visit(AstClassRefDType* nodep) override {
         if (nodep->didWidthAndSet()) return;
         // TODO this maybe eventually required to properly resolve members,
@@ -2485,6 +2528,20 @@ private:
                                   << foundp->warnOther() << "... Location of found object\n"
                                   << foundp->warnContextSecondary());
             }
+        } else if (AstIfaceRefDType* const adtypep = VN_CAST(fromDtp, IfaceRefDType)) {
+            if (AstNode* const foundp = memberSelIface(nodep, adtypep)) {
+                if (AstVar* const varp = VN_CAST(foundp, Var)) {
+                    nodep->dtypep(foundp->dtypep());
+                    nodep->varp(varp);
+                    varp->usedVirtIface(true);
+                    return;
+                }
+                UINFO(1, "found object " << foundp << endl);
+                nodep->v3fatalSrc("MemberSel of non-variable\n"
+                                  << nodep->warnContextPrimary() << '\n'
+                                  << foundp->warnOther() << "... Location of found object\n"
+                                  << foundp->warnContextSecondary());
+            }
         } else if (VN_IS(fromDtp, EnumDType)  //
                    || VN_IS(fromDtp, AssocArrayDType)  //
                    || VN_IS(fromDtp, WildcardArrayDType)  //
@@ -2534,6 +2591,26 @@ private:
                       << (suggest.empty() ? "" : nodep->fileline()->warnMore() + suggest));
         return nullptr;  // Caller handles error
     }
+    AstNode* memberSelIface(AstMemberSel* nodep, AstIfaceRefDType* adtypep) {
+        // Returns node if ok
+        // No need to width-resolve the interface, as it was done when we did the child
+        AstNodeModule* const ifacep = adtypep->ifacep();
+        UASSERT_OBJ(ifacep, nodep, "Unlinked");
+        // if (AstNode* const foundp = ifacep->findMember(nodep->name())) return foundp;
+        VSpellCheck speller;
+        for (AstNode* itemp = ifacep->stmtsp(); itemp; itemp = itemp->nextp()) {
+            if (itemp->name() == nodep->name()) return itemp;
+            if (VN_IS(itemp, Var) || VN_IS(itemp, Modport)) {
+                speller.pushCandidate(itemp->prettyName());
+            }
+        }
+        const string suggest = speller.bestCandidateMsg(nodep->prettyName());
+        nodep->v3error(
+            "Member " << nodep->prettyNameQ() << " not found in interface "
+                      << ifacep->prettyNameQ() << "\n"
+                      << (suggest.empty() ? "" : nodep->fileline()->warnMore() + suggest));
+        return nullptr;  // Caller handles error
+    }
     bool memberSelStruct(AstMemberSel* nodep, AstNodeUOrStructDType* adtypep) {
         // Returns true if ok
         if (AstMemberDType* const memberp = adtypep->findMember(nodep->name())) {
@@ -2595,7 +2672,7 @@ private:
             methodCallClass(nodep, adtypep);
         } else if (AstUnpackArrayDType* const adtypep = VN_CAST(fromDtp, UnpackArrayDType)) {
             methodCallUnpack(nodep, adtypep);
-        } else if (basicp && basicp->isEventValue()) {
+        } else if (basicp && basicp->isEvent()) {
             methodCallEvent(nodep, basicp);
         } else if (basicp && basicp->isString()) {
             methodCallString(nodep, basicp);
@@ -3288,10 +3365,12 @@ private:
     void methodCallEvent(AstMethodCall* nodep, AstBasicDType*) {
         // Method call on event
         if (nodep->name() == "triggered") {
-            // We represent events as numbers, so can just return number
             methodOkArguments(nodep, 0, 0);
-            AstNode* const newp = nodep->fromp()->unlinkFrBack();
-            nodep->replaceWith(newp);
+            AstCMethodHard* const callp = new AstCMethodHard{
+                nodep->fileline(), nodep->fromp()->unlinkFrBack(), "isTriggered"};
+            callp->dtypeSetBit();
+            callp->pure(true);
+            nodep->replaceWith(callp);
             VL_DO_DANGLING(pushDeletep(nodep), nodep);
         } else {
             nodep->v3error("Unknown built-in event method " << nodep->prettyNameQ());
@@ -4236,15 +4315,31 @@ private:
             // if (debug()) nodep->dumpTree(cout, "  AssignOut: ");
         }
         if (const AstBasicDType* const basicp = nodep->rhsp()->dtypep()->basicp()) {
-            if (basicp->isEventValue()) {
+            if (basicp->isEvent()) {
                 // see t_event_copy.v for commentary on the mess involved
                 nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type");
             }
         }
-        if (nodep->timingControlp()) {
-            nodep->timingControlp()->v3warn(
-                ASSIGNDLY, "Unsupported: Ignoring timing control on this assignment.");
-            nodep->timingControlp()->unlinkFrBackWithNext()->deleteTree();
+        if (auto* const controlp = nodep->timingControlp()) {
+            if (VN_IS(m_ftaskp, Func)) {
+                controlp->v3error("Timing controls are not legal in functions. Suggest use a task "
+                                  "(IEEE 1800-2017 13.4.4)");
+                VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp);
+            } else if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) {
+                iterateNull(controlp);
+            } else {
+                if (nodep->fileline()->timingOn()) {
+                    if (v3Global.opt.timing().isSetFalse()) {
+                        controlp->v3warn(ASSIGNDLY, "Ignoring timing control on this "
+                                                    "assignment/primitive due to --no-timing");
+                    } else {
+                        controlp->v3warn(E_NEEDTIMINGOPT,
+                                         "Use --timing or --no-timing to specify how "
+                                         "timing controls should be handled");
+                    }
+                }
+                VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp);
+            }
         }
         if (VN_IS(nodep->rhsp(), EmptyQueue)) {
             UINFO(9, "= {} -> .delete(): " << nodep);
@@ -4307,7 +4402,7 @@ private:
                 fmt = ch;
             } else if (inPct && (isdigit(ch) || ch == '.' || ch == '-')) {
                 fmt += ch;
-            } else if (tolower(inPct)) {
+            } else if (inPct) {
                 inPct = false;
                 bool added = false;
                 switch (tolower(ch)) {
@@ -4646,12 +4741,19 @@ private:
         // TOP LEVEL NODE
         if (nodep->modVarp() && nodep->modVarp()->isGParam()) {
             // Widthing handled as special init() case
+            bool didWidth = false;
             if (auto* const patternp = VN_CAST(nodep->exprp(), Pattern)) {
-                if (const auto* modVarp = nodep->modVarp()) {
-                    patternp->childDTypep(modVarp->childDTypep()->cloneTree(false));
+                if (const AstVar* const modVarp = nodep->modVarp()) {
+                    // Convert BracketArrayDType
+                    userIterate(modVarp->childDTypep(),
+                                WidthVP{SELF, BOTH}.p());  // May relink pointed to node
+                    AstNodeDType* const setDtp = modVarp->childDTypep()->cloneTree(false);
+                    patternp->childDTypep(setDtp);
+                    userIterateChildren(nodep, WidthVP{setDtp, BOTH}.p());
+                    didWidth = true;
                 }
             }
-            userIterateChildren(nodep, WidthVP(SELF, BOTH).p());
+            if (!didWidth) userIterateChildren(nodep, WidthVP(SELF, BOTH).p());
         } else if (!m_paramsOnly) {
             if (!nodep->modVarp()->didWidth()) {
                 // Var hasn't been widthed, so make it so.
@@ -5030,6 +5132,7 @@ private:
                     // (get an ASSIGN with EXTEND on the lhs instead of rhs)
                 }
                 if (!portp->basicp() || portp->basicp()->isOpaque()) {
+                    checkClassAssign(nodep, "Function Argument", pinp, portp->dtypep());
                     userIterate(pinp, WidthVP(portp->dtypep(), FINAL).p());
                 } else {
                     iterateCheckAssign(nodep, "Function Argument", pinp, FINAL, portp->dtypep());
@@ -5054,6 +5157,57 @@ private:
         userIterateChildren(nodep, nullptr);
         m_procedurep = nullptr;
     }
+    void visit(AstSenItem* nodep) override {
+        UASSERT_OBJ(nodep->isClocked(), nodep, "Invalid edge");
+        // Optimize concat/replicate senitems; this has to be done here at the latest, otherwise we
+        // emit WIDTHCONCAT if there are unsized constants
+        if (VN_IS(nodep->sensp(), Concat) || VN_IS(nodep->sensp(), Replicate)) {
+            auto* const concatOrReplp = VN_CAST(nodep->sensp(), NodeBiop);
+            auto* const rhsp = concatOrReplp->rhsp()->unlinkFrBack();
+            if (nodep->edgeType() == VEdgeType::ET_CHANGED) {
+                // If it's ET_CHANGED, split concatenations into multiple senitems
+                auto* const lhsp = concatOrReplp->lhsp()->unlinkFrBack();
+                nodep->addNextHere(new AstSenItem{lhsp->fileline(), nodep->edgeType(), lhsp});
+            }  // Else only use the RHS
+            nodep->replaceWith(new AstSenItem{rhsp->fileline(), nodep->edgeType(), rhsp});
+            VL_DO_DANGLING(nodep->deleteTree(), nodep);
+        } else {
+            userIterateChildren(nodep, WidthVP(SELF, BOTH).p());
+            if (nodep->edgeType().anEdge() && nodep->sensp()->dtypep()->skipRefp()->isDouble()) {
+                nodep->sensp()->v3error(
+                    "Edge event control not legal on real type (IEEE 1800-2017 6.12.1)");
+            }
+        }
+    }
+    void visit(AstWait* nodep) override {
+        if (VN_IS(m_ftaskp, Func)) {
+            nodep->v3error("Wait statements are not legal in functions. Suggest use a task "
+                           "(IEEE 1800-2017 13.4.4)");
+            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
+            return;
+        }
+        if (nodep->fileline()->timingOn()) {
+            if (v3Global.opt.timing().isSetTrue()) {
+                userIterate(nodep->condp(), WidthVP(SELF, PRELIM).p());
+                iterateNull(nodep->stmtsp());
+                return;
+            } else if (v3Global.opt.timing().isSetFalse()) {
+                nodep->v3warn(E_NOTIMING, "Wait statements require --timing");
+            } else {
+                nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how wait "
+                                               "statements should be handled");
+            }
+        }
+        // If we ignore timing:
+        // Statements we'll just execute immediately; equivalent to if they followed this
+        if (AstNode* const stmtsp = nodep->stmtsp()) {
+            stmtsp->unlinkFrBackWithNext();
+            nodep->replaceWith(stmtsp);
+        } else {
+            nodep->unlinkFrBack();
+        }
+        VL_DO_DANGLING(nodep->deleteTree(), nodep);
+    }
     void visit(AstWith* nodep) override {
         // Should otherwise be underneath a method call
         AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp();
@@ -5229,7 +5383,13 @@ private:
             userIterateAndNext(nodep->lhsp(), WidthVP(CONTEXT, PRELIM).p());
             userIterateAndNext(nodep->rhsp(), WidthVP(CONTEXT, PRELIM).p());
             if (nodep->lhsp()->isDouble() || nodep->rhsp()->isDouble()) {
-                if (!realok) nodep->v3error("Real not allowed as operand to in ?== operator");
+                if (!realok) {
+                    nodep->v3error("Real is illegal operand to ?== operator");
+                    AstNode* const newp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
+                    nodep->replaceWith(newp);
+                    VL_DO_DANGLING(pushDeletep(nodep), nodep);
+                    return;
+                }
                 if (AstNodeBiop* const newp = replaceWithDVersion(nodep)) {
                     VL_DANGLING(nodep);
                     nodep = newp;  // Process new node instead
@@ -5582,7 +5742,8 @@ private:
         // node, while the output dtype is the *expected* sign.
         // It is reasonable to have sign extension with unsigned output,
         // for example $unsigned(a)+$signed(b), the SIGNED(B) will be unsigned dtype out
-        UINFO(4, "  widthExtend_(r=" << extendRule << ") old: " << nodep << endl);
+        UINFO(4,
+              "  widthExtend_(r=" << static_cast(extendRule) << ") old: " << nodep << endl);
         if (extendRule == EXTEND_OFF) return;
         AstConst* const constp = VN_CAST(nodep, Const);
         const int expWidth = expDTypep->width();
@@ -5700,6 +5861,15 @@ private:
         return false;  // No change
     }
 
+    void checkClassAssign(AstNode* nodep, const char* side, AstNode* rhsp,
+                          AstNodeDType* lhsDTypep) {
+        if (VN_IS(lhsDTypep, ClassRefDType) && !VN_IS(rhsp->dtypep(), ClassRefDType)) {
+            if (auto* const constp = VN_CAST(rhsp, Const)) {
+                if (constp->num().isNull()) return;
+            }
+            nodep->v3error(side << " expects a " << lhsDTypep->prettyTypeName());
+        }
+    }
     static bool similarDTypeRecurse(AstNodeDType* node1p, AstNodeDType* node2p) {
         return node1p->skipRefp()->similarDType(node2p->skipRefp());
     }
@@ -5782,6 +5952,7 @@ private:
         // if (debug()) nodep->dumpTree(cout, "-checkass: ");
         UASSERT_OBJ(stage == FINAL, nodep, "Bad width call");
         // We iterate and size the RHS based on the result of RHS evaluation
+        checkClassAssign(nodep, side, rhsp, lhsDTypep);
         const bool lhsStream
             = (VN_IS(nodep, NodeAssign) && VN_IS(VN_AS(nodep, NodeAssign)->lhsp(), NodeStream));
         rhsp = iterateCheck(nodep, side, rhsp, ASSIGN, FINAL, lhsDTypep,
@@ -5904,11 +6075,38 @@ private:
                 // Note the check uses the expected size, not the child's subDTypep as we want the
                 // child node's width to end up correct for the assignment (etc)
                 widthCheckSized(nodep, side, underp, expDTypep, extendRule, warnOn);
-            } else if (!VN_IS(expDTypep, IfaceRefDType)
-                       && VN_IS(underp->dtypep(), IfaceRefDType)) {
+            } else if (!VN_IS(expDTypep->skipRefp(), IfaceRefDType)
+                       && VN_IS(underp->dtypep()->skipRefp(), IfaceRefDType)) {
                 underp->v3error(ucfirst(nodep->prettyOperatorName())
                                 << " expected non-interface on " << side << " but '"
                                 << underp->name() << "' is an interface.");
+            } else if (const AstIfaceRefDType* expIfaceRefp
+                       = VN_CAST(expDTypep->skipRefp(), IfaceRefDType)) {
+                const AstIfaceRefDType* underIfaceRefp
+                    = VN_CAST(underp->dtypep()->skipRefp(), IfaceRefDType);
+                if (!underIfaceRefp) {
+                    underp->v3error(ucfirst(nodep->prettyOperatorName())
+                                    << " expected " << expIfaceRefp->ifaceViaCellp()->prettyNameQ()
+                                    << " interface on " << side << " but " << underp->prettyNameQ()
+                                    << " is not an interface.");
+                } else if (expIfaceRefp->ifaceViaCellp() != underIfaceRefp->ifaceViaCellp()) {
+                    underp->v3error(ucfirst(nodep->prettyOperatorName())
+                                    << " expected " << expIfaceRefp->ifaceViaCellp()->prettyNameQ()
+                                    << " interface on " << side << " but '" << underp->name()
+                                    << "' is a different interface ("
+                                    << underIfaceRefp->ifaceViaCellp()->prettyNameQ() << ").");
+                } else if (underIfaceRefp->modportp()
+                           && expIfaceRefp->modportp() != underIfaceRefp->modportp()) {
+                    underp->v3error(ucfirst(nodep->prettyOperatorName())
+                                    << " expected "
+                                    << (expIfaceRefp->modportp()
+                                            ? expIfaceRefp->modportp()->prettyNameQ()
+                                            : "no")
+                                    << " interface modport on " << side << " but got "
+                                    << underIfaceRefp->modportp()->prettyNameQ() << " modport.");
+                } else {
+                    underp = userIterateSubtreeReturnEdits(underp, WidthVP{expDTypep, FINAL}.p());
+                }
             } else {
                 // Hope it just works out (perhaps a cast will deal with it)
                 underp = userIterateSubtreeReturnEdits(underp, WidthVP(expDTypep, FINAL).p());
diff --git a/src/Verilator.cpp b/src/Verilator.cpp
index 448bf735d..9604eb151 100644
--- a/src/Verilator.cpp
+++ b/src/Verilator.cpp
@@ -27,7 +27,6 @@
 #include "V3Case.h"
 #include "V3Cast.h"
 #include "V3Cdc.h"
-#include "V3Changed.h"
 #include "V3Class.h"
 #include "V3Clean.h"
 #include "V3Clock.h"
@@ -41,6 +40,7 @@
 #include "V3Depth.h"
 #include "V3DepthBlock.h"
 #include "V3Descope.h"
+#include "V3DfgOptimizer.h"
 #include "V3EmitC.h"
 #include "V3EmitCMain.h"
 #include "V3EmitCMake.h"
@@ -51,7 +51,6 @@
 #include "V3File.h"
 #include "V3Force.h"
 #include "V3Gate.h"
-#include "V3GenClk.h"
 #include "V3Global.h"
 #include "V3Graph.h"
 #include "V3HierBlock.h"
@@ -69,7 +68,6 @@
 #include "V3Localize.h"
 #include "V3MergeCond.h"
 #include "V3Name.h"
-#include "V3Order.h"
 #include "V3Os.h"
 #include "V3Param.h"
 #include "V3ParseSym.h"
@@ -79,6 +77,7 @@
 #include "V3ProtectLib.h"
 #include "V3Randomize.h"
 #include "V3Reloop.h"
+#include "V3Sched.h"
 #include "V3Scope.h"
 #include "V3Scoreboard.h"
 #include "V3Slice.h"
@@ -91,6 +90,7 @@
 #include "V3TSP.h"
 #include "V3Table.h"
 #include "V3Task.h"
+#include "V3Timing.h"
 #include "V3Trace.h"
 #include "V3TraceDecl.h"
 #include "V3Tristate.h"
@@ -189,7 +189,7 @@ static void process() {
 
     // Push constants, but only true constants preserving liveness
     // so V3Undriven sees variables to be eliminated, ie "if (0 && foo) ..."
-    V3Const::constifyAllLive(v3Global.rootp());
+    if (v3Global.opt.fConstBeforeDfg()) V3Const::constifyAllLive(v3Global.rootp());
 
     // Signal based lint checks, no change to structures
     // Must be before first constification pass drops dead code
@@ -209,7 +209,7 @@ static void process() {
     }
 
     // Propagate constants into expressions
-    V3Const::constifyAllLint(v3Global.rootp());
+    if (v3Global.opt.fConstBeforeDfg()) V3Const::constifyAllLint(v3Global.rootp());
 
     if (!(v3Global.opt.xmlOnly() && !v3Global.opt.flatten())) {
         // Split packed variables into multiple pieces to resolve UNOPTFLAT.
@@ -236,6 +236,16 @@ static void process() {
         v3Global.constRemoveXs(true);
     }
 
+    if (v3Global.opt.fDfgPreInline() || v3Global.opt.fDfgPostInline()) {
+        // If doing DFG optimization, extract some additional candidates
+        V3DfgOptimizer::extract(v3Global.rootp());
+    }
+
+    if (v3Global.opt.fDfgPreInline()) {
+        // Pre inline DFG optimization
+        V3DfgOptimizer::optimize(v3Global.rootp(), " pre inline");
+    }
+
     if (!(v3Global.opt.xmlOnly() && !v3Global.opt.flatten())) {
         // Module inlining
         // Cannot remove dead variables after this, as alias information for final
@@ -246,6 +256,11 @@ static void process() {
         }
     }
 
+    if (v3Global.opt.fDfgPostInline()) {
+        // Post inline DFG optimization
+        V3DfgOptimizer::optimize(v3Global.rootp(), "post inline");
+    }
+
     // --PRE-FLAT OPTIMIZATIONS------------------
 
     // Initial const/dead to reduce work for ordering code
@@ -366,6 +381,14 @@ static void process() {
         // Reorder assignments in pipelined blocks
         if (v3Global.opt.fReorder()) V3Split::splitReorderAll(v3Global.rootp());
 
+        if (v3Global.opt.timing().isSetTrue()) {
+            // Convert AST for timing if requested
+            // Needs to be after V3Gate, as that step modifies sentrees
+            // Needs to be before V3Delayed, as delayed assignments are handled differently in
+            // suspendable processes
+            V3Timing::timingAll(v3Global.rootp());
+        }
+
         // Create delayed assignments
         // This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step
         V3Delayed::delayedAll(v3Global.rootp());
@@ -377,11 +400,8 @@ static void process() {
 
         if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "PreOrder");
 
-        // Order the code; form SBLOCKs and BLOCKCALLs
-        V3Order::orderAll(v3Global.rootp());
-
-        // Change generated clocks to look at delayed signals
-        V3GenClk::genClkAll(v3Global.rootp());
+        // Schedule the logic
+        V3Sched::schedule(v3Global.rootp());
 
         // Convert sense lists into IF statements.
         V3Clock::clockAll(v3Global.rootp());
@@ -393,15 +413,13 @@ static void process() {
             V3Const::constifyAll(v3Global.rootp());
             V3Life::lifeAll(v3Global.rootp());
         }
+
         if (v3Global.opt.fLifePost()) V3LifePost::lifepostAll(v3Global.rootp());
 
         // Remove unused vars
         V3Const::constifyAll(v3Global.rootp());
         V3Dead::deadifyAllScoped(v3Global.rootp());
 
-        // Detect change loop
-        V3Changed::changedAll(v3Global.rootp());
-
         // Create tracing logic, since we ripped out some signals the user might want to trace
         // Note past this point, we presume traced variables won't move between CFuncs
         // (It's OK if untraced temporaries move around, or vars
@@ -541,6 +559,9 @@ static void process() {
 
     // Output DPI protected library files
     if (!v3Global.opt.libCreate().empty()) {
+        if (v3Global.rootp()->delaySchedulerp()) {
+            v3warn(E_UNSUPPORTED, "Unsupported: --lib-create with --timing and delays");
+        }
         V3ProtectLib::protect();
         V3EmitV::emitvFiles();
         V3EmitC::emitcFiles();
diff --git a/src/astgen b/src/astgen
index dbe6968e0..2c7f847aa 100755
--- a/src/astgen
+++ b/src/astgen
@@ -8,12 +8,14 @@ import os
 import re
 import sys
 import textwrap
+
 # from pprint import pprint, pformat
 
 
+# This class is used to represents both AstNode and DfgVertex sub-types
 class Node:
 
-    def __init__(self, name, superClass, file, lineno):
+    def __init__(self, name, superClass, file=None, lineno=None):
         self._name = name
         self._superClass = superClass
         self._subClasses = []  # Initially list, but tuple after completion
@@ -173,8 +175,11 @@ class Node:
         return self in other.allSubClasses
 
 
-Nodes = {}
-SortedNodes = None
+AstNodes = {}
+AstNodeList = None
+
+DfgVertices = {}
+DfgVertexList = None
 
 ClassRefs = {}
 Stages = {}
@@ -277,7 +282,7 @@ class Cpt:
                 self.error("Can't parse from function: " + func)
             typen = match.group(1)
             subnodes = match.group(2)
-            if Nodes[typen].isRoot:
+            if AstNodes[typen].isRoot:
                 self.error("Unknown AstNode typen: " + typen + ": in " + func)
 
             mif = ""
@@ -332,7 +337,7 @@ class Cpt:
         elif match_skip:
             typen = match_skip.group(1)
             self.tree_skip_visit[typen] = 1
-            if typen not in Nodes:
+            if typen not in AstNodes:
                 self.error("Unknown node type: " + typen)
 
         else:
@@ -462,7 +467,7 @@ class Cpt:
         self.print(
             "    // Bottom class up, as more simple transforms are generally better\n"
         )
-        for node in SortedNodes:
+        for node in AstNodeList:
             out_for_type_sc = []
             out_for_type = []
             classes = list(node.allSuperClasses)
@@ -501,7 +506,7 @@ class Cpt:
                     "".join(out_for_type_sc))
                 if out_for_type[0]:
                     self.print("      iterateAndNextNull(nodep->rhsp());\n")
-                    if node.isSubClassOf(Nodes["NodeTriop"]):
+                    if node.isSubClassOf(AstNodes["NodeTriop"]):
                         self.print(
                             "      iterateAndNextNull(nodep->thsp());\n")
                     self.print("".join(out_for_type) + "    }\n")
@@ -541,7 +546,7 @@ def parseOpType(string):
     return None
 
 
-def read_types(filename):
+def read_types(filename, Nodes, prefix):
     hasErrors = False
 
     def error(lineno, message):
@@ -559,8 +564,9 @@ def read_types(filename):
             return
         if not hasAstgenMembers:
             error(
-                node.lineno, "'Ast" + node.name +
-                "' does not contain 'ASTGEN_MEMBERS_" + node.name + ";'")
+                node.lineno,
+                "'{p}{n}' does not contain 'ASTGEN_MEMBERS_{p}{n};'".format(
+                    p=prefix, n=node.name))
         hasAstgenMembers = False
 
     with open(filename) as fh:
@@ -574,12 +580,12 @@ def read_types(filename):
                 classn = match.group(2)
                 match = re.search(r':\s*public\s+(\S+)', line)
                 supern = match.group(1) if match else ""
-                if re.search(r'Ast', supern):
-                    classn = re.sub(r'^Ast', '', classn)
-                    supern = re.sub(r'^Ast', '', supern)
+                if re.search(prefix, supern):
+                    classn = re.sub(r'^' + prefix, '', classn)
+                    supern = re.sub(r'^' + prefix, '', supern)
                     if not supern:
-                        sys.exit("%Error: 'Ast{}' has no super-class".format(
-                            classn))
+                        sys.exit("%Error: '{p}{c}' has no super-class".format(
+                            p=prefix, c=classn))
                     checkFinishedNode(node)
                     superClass = Nodes[supern]
                     node = Node(classn, superClass, filename, lineno)
@@ -588,8 +594,13 @@ def read_types(filename):
             if not node:
                 continue
 
-            if re.match(r'^\s*ASTGEN_MEMBERS_' + node.name + ';', line):
+            if re.match(r'^\s*ASTGEN_MEMBERS_' + prefix + node.name + ';',
+                        line):
                 hasAstgenMembers = True
+
+            if prefix != "Ast":
+                continue
+
             match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line)
             if match:
                 decl = re.sub(r'//.*$', '', match.group(1))
@@ -649,6 +660,59 @@ def read_types(filename):
         sys.exit("%Error: Stopping due to errors reported above")
 
 
+def check_types(sortedTypes, prefix, abstractPrefix):
+    baseClass = prefix + abstractPrefix
+
+    # Check all leaf types are not AstNode* and non-leaves are AstNode*
+    for node in sortedTypes:
+        if re.match(r'^' + abstractPrefix, node.name):
+            if node.isLeaf:
+                sys.exit(
+                    "%Error: Final {b} subclasses must not be named {b}*: {p}{n}"
+                    .format(b=baseClass, p=prefix, n=node.name))
+        else:
+            if not node.isLeaf:
+                sys.exit(
+                    "%Error: Non-final {b} subclasses must be named {b}*: {p}{n}"
+                    .format(b=baseClass, p=prefix, n=node.name))
+
+    # Check ordering of node definitions
+    hasOrderingError = False
+
+    files = tuple(
+        sorted(set(_.file for _ in sortedTypes if _.file is not None)))
+
+    for file in files:
+        nodes = tuple(filter(lambda _, f=file: _.file == f, sortedTypes))
+        expectOrder = tuple(sorted(nodes, key=lambda _: (_.isLeaf, _.ordIdx)))
+        actualOrder = tuple(sorted(nodes, key=lambda _: _.lineno))
+        expect = {
+            node: pred
+            for pred, node in zip((None, ) + expectOrder[:-1], expectOrder)
+        }
+        actual = {
+            node: pred
+            for pred, node in zip((None, ) + actualOrder[:-1], actualOrder)
+        }
+        for node in nodes:
+            if expect[node] != actual[node]:
+                hasOrderingError = True
+                pred = expect[node]
+                print(
+                    "{file}:{lineno}: %Error: Definition of '{p}{n}' is out of order. Shold be {where}."
+                    .format(file=file,
+                            lineno=node.lineno,
+                            p=prefix,
+                            n=node.name,
+                            where=("right after '" + prefix + pred.name +
+                                   "'" if pred else "first in file")),
+                    file=sys.stderr)
+
+    if hasOrderingError:
+        sys.exit(
+            "%Error: Stopping due to out of order definitions listed above")
+
+
 def read_stages(filename):
     with open(filename) as fh:
         n = 100
@@ -711,7 +775,7 @@ def write_report(filename):
             fh.write("  " + classn + "\n")
 
         fh.write("\nClasses:\n")
-        for node in SortedNodes:
+        for node in AstNodeList:
             fh.write("  class Ast%-17s\n" % node.name)
             fh.write("    arity:  {}\n".format(node.arity))
             fh.write("    parent: ")
@@ -740,97 +804,110 @@ def write_report(filename):
             fh.write("\n")
 
 
-def write_classes(filename):
-    with open_file(filename) as fh:
-        fh.write("class AstNode;\n")
-        for node in SortedNodes:
-            fh.write("class Ast%-17s // " % (node.name + ";"))
+################################################################################
+# Common code genaration
+################################################################################
+
+
+def write_forward_class_decls(prefix, nodeList):
+    with open_file("V3{p}__gen_forward_class_decls.h".format(p=prefix)) as fh:
+        for node in nodeList:
+            fh.write("class {p}{n:<17} // ".format(p=prefix,
+                                                   n=node.name + ";"))
             for superClass in node.allSuperClasses:
-                fh.write("Ast%-12s " % superClass.name)
+                fh.write("{p}{n:<12} ".format(p=prefix, n=superClass.name))
             fh.write("\n")
 
 
-def write_visitor_decls(filename):
-    with open_file(filename) as fh:
-        for node in SortedNodes:
+def write_visitor_decls(prefix, nodeList):
+    with open_file("V3{p}__gen_visitor_decls.h".format(p=prefix)) as fh:
+        for node in nodeList:
             if not node.isRoot:
-                fh.write("virtual void visit(Ast" + node.name + "*);\n")
+                fh.write("virtual void visit({p}{n}*);\n".format(p=prefix,
+                                                                 n=node.name))
 
 
-def write_visitor_defns(filename):
-    with open_file(filename) as fh:
-        for node in SortedNodes:
+def write_visitor_defns(prefix, nodeList, visitor):
+    with open_file("V3{p}__gen_visitor_defns.h".format(p=prefix)) as fh:
+        variable = "nodep" if prefix == "Ast" else "vtxp"
+        for node in nodeList:
             base = node.superClass
             if base is not None:
-                fh.write("void VNVisitor::visit(Ast" + node.name +
-                         "* nodep) { visit(static_cast(nodep)); }\n")
+                fh.write(
+                    "void {c}::visit({p}{n}* {v}) {{ visit(static_cast<{p}{b}*>({v})); }}\n"
+                    .format(c=visitor,
+                            p=prefix,
+                            n=node.name,
+                            b=base.name,
+                            v=variable))
 
 
-def write_impl(filename):
-    with open_file(filename) as fh:
-        fh.write("\n")
-        fh.write("// For internal use. They assume argument is not nullptr.\n")
-        for node in SortedNodes:
-            fh.write("template<> inline bool AstNode::privateTypeTest(const AstNode* nodep) { ")
-            if node.isRoot:
-                fh.write("return true; ")
-            else:
-                fh.write("return ")
-                if not node.isLeaf:
-                    fh.write(
-                        "static_cast(nodep->type()) >= static_cast(VNType::first"
-                        + node.name + ") && ")
-                    fh.write(
-                        "static_cast(nodep->type()) <= static_cast(VNType::last"
-                        + node.name + "); ")
-                else:
-                    fh.write("nodep->type() == VNType::at" + node.name + "; ")
-            fh.write("}\n")
+def write_type_enum(prefix, nodeList):
+    root = next(_ for _ in nodeList if _.isRoot)
+    with open_file("V3{p}__gen_type_enum.h".format(p=prefix)) as fh:
 
-
-def write_types(filename):
-    with open_file(filename) as fh:
         fh.write("    enum en : uint16_t {\n")
-        for node in sorted(filter(lambda _: _.isLeaf, SortedNodes),
+        for node in sorted(filter(lambda _: _.isLeaf, nodeList),
                            key=lambda _: _.typeId):
-            fh.write("        at" + node.name + " = " + str(node.typeId) +
-                     ",\n")
-        fh.write("        _ENUM_END = " + str(Nodes["Node"].typeIdMax + 1) +
-                 "\n")
+            fh.write("        at{t} = {n},\n".format(t=node.name,
+                                                     n=node.typeId))
+        fh.write("        _ENUM_END = {n}\n".format(n=root.typeIdMax + 1))
         fh.write("    };\n")
 
         fh.write("    enum bounds : uint16_t {\n")
-        for node in sorted(filter(lambda _: not _.isLeaf, SortedNodes),
+        for node in sorted(filter(lambda _: not _.isLeaf, nodeList),
                            key=lambda _: _.typeIdMin):
-            fh.write("        first" + node.name + " = " +
-                     str(node.typeIdMin) + ",\n")
-            fh.write("        last" + node.name + " = " + str(node.typeIdMax) +
-                     ",\n")
+            fh.write("        first{t} = {n},\n".format(t=node.name,
+                                                        n=node.typeIdMin))
+            fh.write("        last{t}  = {n},\n".format(t=node.name,
+                                                        n=node.typeIdMax))
         fh.write("        _BOUNDS_END\n")
         fh.write("    };\n")
 
         fh.write("    const char* ascii() const {\n")
         fh.write("        static const char* const names[_ENUM_END + 1] = {\n")
-        for node in sorted(filter(lambda _: _.isLeaf, SortedNodes),
+        for node in sorted(filter(lambda _: _.isLeaf, nodeList),
                            key=lambda _: _.typeId):
-            fh.write("            \"" + node.name.upper() + "\",\n")
+            fh.write('            "{T}",\n'.format(T=node.name.upper()))
         fh.write("            \"_ENUM_END\"\n")
         fh.write("        };\n")
         fh.write("        return names[m_e];\n")
         fh.write("    }\n")
 
 
-def write_yystype(filename):
-    with open_file(filename) as fh:
-        for node in SortedNodes:
-            fh.write("Ast{t}* {m}p;\n".format(t=node.name,
-                                              m=node.name[0].lower() +
-                                              node.name[1:]))
+def write_type_tests(prefix, nodeList):
+    with open_file("V3{p}__gen_type_tests.h".format(p=prefix)) as fh:
+        fh.write("// For internal use. They assume argument is not nullptr.\n")
+        if prefix == "Ast":
+            base = "AstNode"
+            variable = "nodep"
+            enum = "VNType"
+        elif prefix == "Dfg":
+            base = "DfgVertex"
+            variable = "vtxp"
+            enum = "VDfgType"
+        for node in nodeList:
+            fh.write(
+                "template<> inline bool {b}::privateTypeTest<{p}{n}>(const {b}* {v}) {{ "
+                .format(b=base, p=prefix, n=node.name, v=variable))
+            if node.isRoot:
+                fh.write("return true;")
+            elif not node.isLeaf:
+                fh.write(
+                    "return static_cast({v}->type()) >= static_cast({e}::first{t}) && static_cast({v}->type()) <= static_cast({e}::last{t});"
+                    .format(v=variable, e=enum, t=node.name))
+            else:
+                fh.write("return {v}->type() == {e}::at{t};".format(
+                    v=variable, e=enum, t=node.name))
+            fh.write(" }\n")
 
 
-def write_macros(filename):
+################################################################################
+# Ast code genaration
+################################################################################
+
+
+def write_ast_macros(filename):
     with open_file(filename) as fh:
 
         def emitBlock(pattern, **fmt):
@@ -838,8 +915,8 @@ def write_macros(filename):
                 textwrap.indent(textwrap.dedent(pattern),
                                 "    ").format(**fmt).replace("\n", " \\\n"))
 
-        for node in SortedNodes:
-            fh.write("#define ASTGEN_MEMBERS_{t} \\\n".format(t=node.name))
+        for node in AstNodeList:
+            fh.write("#define ASTGEN_MEMBERS_Ast{t} \\\n".format(t=node.name))
             emitBlock('''\
             static Ast{t}* cloneTreeNull(Ast{t}* nodep, bool cloneNextLink) {{
                 return nodep ? nodep->cloneTree(cloneNextLink) : nullptr;
@@ -847,7 +924,7 @@ def write_macros(filename):
             Ast{t}* cloneTree(bool cloneNext) {{
                 return static_cast(AstNode::cloneTree(cloneNext));
             }}
-            Ast{t}* clonep() const {{ return static_cast(AstNode::clonep()); }}
+            Ast{t}* clonep() const VL_MT_SAFE {{ return static_cast(AstNode::clonep()); }}
             Ast{t}* addNext(Ast{t}* nodep) {{ return static_cast(AstNode::addNext(this, nodep)); }}
             ''',
                       t=node.name)
@@ -859,7 +936,7 @@ def write_macros(filename):
                 ''',
                           t=node.name)
 
-            for n in (1, 2, 3, 4):
+            for n in range(1, 5):
                 op = node.getOp(n)
                 if not op:
                     continue
@@ -868,7 +945,7 @@ def write_macros(filename):
                             "op{n}p()").format(n=n, kind=kind)
                 if monad == "List":
                     emitBlock('''\
-                    Ast{kind}* {name}() const {{ return {retrieve}; }}
+                    Ast{kind}* {name}() const VL_MT_SAFE {{ return {retrieve}; }}
                     void add{Name}(Ast{kind}* nodep) {{ addNOp{n}p(reinterpret_cast(nodep)); }}
                     ''',
                               kind=kind,
@@ -878,7 +955,7 @@ def write_macros(filename):
                               retrieve=retrieve)
                 elif monad == "Optional":
                     emitBlock('''\
-                    Ast{kind}* {name}() const {{ return {retrieve}; }}
+                    Ast{kind}* {name}() const VL_MT_SAFE {{ return {retrieve}; }}
                     void {name}(Ast{kind}* nodep) {{ setNOp{n}p(reinterpret_cast(nodep)); }}
                     ''',
                               kind=kind,
@@ -887,7 +964,7 @@ def write_macros(filename):
                               retrieve=retrieve)
                 else:
                     emitBlock('''\
-                    Ast{kind}* {name}() const {{ return {retrieve}; }}
+                    Ast{kind}* {name}() const VL_MT_SAFE {{ return {retrieve}; }}
                     void {name}(Ast{kind}* nodep) {{ setOp{n}p(reinterpret_cast(nodep)); }}
                     ''',
                               kind=kind,
@@ -906,7 +983,15 @@ def write_macros(filename):
             fh.write("\n")
 
 
-def write_op_checks(filename):
+def write_ast_yystype(filename):
+    with open_file(filename) as fh:
+        for node in AstNodeList:
+            fh.write("Ast{t}* {m}p;\n".format(t=node.name,
+                                              m=node.name[0].lower() +
+                                              node.name[1:]))
+
+
+def write_ast_op_checks(filename):
     with open_file(filename) as fh:
 
         indent = ""
@@ -916,7 +1001,7 @@ def write_op_checks(filename):
                 textwrap.indent(textwrap.dedent(pattern),
                                 indent).format(**fmt))
 
-        for node in SortedNodes:
+        for node in AstNodeList:
             if not node.isLeaf:
                 continue
 
@@ -963,6 +1048,7 @@ def write_op_checks(filename):
                                     backp = tailp = opp;
                                     opp = {next};
                                 }} while (opp);
+                                if (headp && tailp) {{}}  // Prevent unused
                                 UASSERT_OBJ(headp->m_headtailp == tailp, headp, "Tail in headtailp is inconsistent");
                                 UASSERT_OBJ(tailp->m_headtailp == headp, tailp, "Head in headtailp is inconsistent");
                             }}
@@ -989,6 +1075,137 @@ def write_op_checks(filename):
                 ''')
 
 
+################################################################################
+# DFG code genaration
+################################################################################
+
+
+def write_dfg_macros(filename):
+    with open_file(filename) as fh:
+
+        def emitBlock(pattern, **fmt):
+            fh.write(
+                textwrap.indent(textwrap.dedent(pattern),
+                                "    ").format(**fmt).replace("\n", " \\\n"))
+
+        for node in DfgVertexList:
+            fh.write("#define ASTGEN_MEMBERS_Dfg{t} \\\n".format(t=node.name))
+
+            if node.isLeaf:
+                emitBlock('''\
+                static constexpr VDfgType dfgType() {{ return VDfgType::at{t}; }};
+                void accept(DfgVisitor& v) override {{ v.visit(this); }}
+                ''',
+                          t=node.name)
+
+            for n in range(1, node.arity + 1):
+                name, _, _ = node.getOp(n)
+                emitBlock('''\
+                DfgVertex* {name}() const {{ return source<{n}>(); }}
+                void {name}(DfgVertex* vtxp) {{ relinkSource<{n}>(vtxp); }}
+                ''',
+                          name=name,
+                          n=n - 1)
+
+            operandNames = tuple(
+                node.getOp(n)[0] for n in range(1, node.arity + 1))
+            if operandNames:
+                emitBlock('''\
+                          const std::string srcName(size_t idx) const override {{
+                              static const char* names[{a}] = {{ {ns} }};
+                              return names[idx];
+                          }}
+                          ''',
+                          a=node.arity,
+                          ns=", ".join(
+                              map(lambda _: '"' + _ + '"', operandNames)))
+            fh.write(
+                "    static_assert(true, \"\")\n")  # Swallowing the semicolon
+
+
+def write_dfg_auto_classes(filename):
+    with open_file(filename) as fh:
+
+        def emitBlock(pattern, **fmt):
+            fh.write(textwrap.dedent(pattern).format(**fmt))
+
+        for node in DfgVertexList:
+            # Only generate code for automatically derieved leaf nodes
+            if (node.file is not None) or not node.isLeaf:
+                continue
+
+            emitBlock('''\
+                      class Dfg{t} final : public Dfg{s} {{
+                      public:
+                          Dfg{t}(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
+                              : Dfg{s}{{dfg, dfgType(), flp, dtypep}} {{}}
+                          ASTGEN_MEMBERS_Dfg{t};
+                      }};
+                      ''',
+                      t=node.name,
+                      s=node.superClass.name)
+        fh.write("\n")
+
+
+def write_dfg_ast_to_dfg(filename):
+    with open_file(filename) as fh:
+        for node in DfgVertexList:
+            # Only generate code for automatically derieved leaf nodes
+            if (node.file is not None) or (not node.isLeaf):
+                continue
+
+            fh.write(
+                "void visit(Ast{t}* nodep) override {{\n".format(t=node.name))
+            fh.write(
+                '    UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");\n\n'
+            )
+            fh.write("    if (unhandled(nodep)) return;\n\n")
+            for i in range(node.arity):
+                fh.write("    iterate(nodep->op{j}p());\n".format(j=i + 1))
+                fh.write("    if (m_foundUnhandled) return;\n")
+                fh.write(
+                    '    UASSERT_OBJ(nodep->op{j}p()->user1p(), nodep, "Child {j} missing Dfg vertex");\n'
+                    .format(j=i + 1))
+            fh.write("\n")
+            fh.write(
+                "    Dfg{t}* const vtxp = makeVertex(nodep, *m_dfgp);\n"
+                .format(t=node.name))
+            fh.write("    if (!vtxp) {\n")
+            fh.write("        m_foundUnhandled = true;\n")
+            fh.write("        ++m_ctx.m_nonRepNode;\n")
+            fh.write("        return;\n")
+            fh.write("    }\n\n")
+            for i in range(node.arity):
+                fh.write(
+                    "    vtxp->relinkSource<{i}>(nodep->op{j}p()->user1u().to());\n"
+                    .format(i=i, j=i + 1))
+            fh.write("\n")
+            fh.write("    m_uncommittedVertices.push_back(vtxp);\n")
+            fh.write("    nodep->user1p(vtxp);\n")
+            fh.write("}\n")
+
+
+def write_dfg_dfg_to_ast(filename):
+    with open_file(filename) as fh:
+        for node in DfgVertexList:
+            # Only generate code for automatically derieved leaf nodes
+            if (node.file is not None) or (not node.isLeaf):
+                continue
+
+            fh.write(
+                "void visit(Dfg{t}* vtxp) override {{\n".format(t=node.name))
+            for i in range(node.arity):
+                fh.write(
+                    "    AstNodeMath* const op{j}p = convertSource(vtxp->source<{i}>());\n"
+                    .format(i=i, j=i + 1))
+            fh.write(
+                "    m_resultp = makeNode(vtxp".format(t=node.name))
+            for i in range(node.arity):
+                fh.write(", op{j}p".format(j=i + 1))
+            fh.write(");\n")
+            fh.write("}\n")
+
+
 ######################################################################
 # main
 
@@ -1008,6 +1225,9 @@ parser.add_argument('-I', action='store', help='source code include directory')
 parser.add_argument('--astdef',
                     action='append',
                     help='add AST definition file (relative to -I)')
+parser.add_argument('--dfgdef',
+                    action='append',
+                    help='add DFG definition file (relative to -I)')
 parser.add_argument('--classes',
                     action='store_true',
                     help='makes class declaration files')
@@ -1017,61 +1237,86 @@ parser.add_argument('infiles', nargs='*', help='list of input .cpp filenames')
 
 Args = parser.parse_args()
 
+###############################################################################
+# Read AstNode definitions
+###############################################################################
+
 # Set up the root AstNode type. It is standalone so we don't need to parse the
 # sources for this.
-Nodes["Node"] = Node("Node", None, "AstNode", 1)
+AstNodes["Node"] = Node("Node", None)
 
-# Read Ast node definitions
+# Read AstNode definitions
 for filename in Args.astdef:
-    read_types(os.path.join(Args.I, filename))
+    read_types(os.path.join(Args.I, filename), AstNodes, "Ast")
 
 # Compute derived properties over the whole AstNode hierarchy
-Nodes["Node"].complete()
+AstNodes["Node"].complete()
 
-SortedNodes = tuple(map(lambda _: Nodes[_], sorted(Nodes.keys())))
+AstNodeList = tuple(map(lambda _: AstNodes[_], sorted(AstNodes.keys())))
 
-for node in SortedNodes:
-    # Check all leaves are not AstNode* and non-leaves are AstNode*
-    if re.match(r'^Node', node.name):
-        if node.isLeaf:
-            sys.exit(
-                "%Error: Final AstNode subclasses must not be named AstNode*: Ast"
-                + node.name)
+check_types(AstNodeList, "Ast", "Node")
+
+###############################################################################
+# Read and generate DfgVertex definitions
+###############################################################################
+
+# Set up the root DfgVertex type and some other hand-written base types.
+# These are standalone so we don't need to parse the sources for this.
+DfgVertices["Vertex"] = Node("Vertex", None)
+DfgVertices["VertexUnary"] = Node("VertexUnary", DfgVertices["Vertex"])
+DfgVertices["Vertex"].addSubClass(DfgVertices["VertexUnary"])
+DfgVertices["VertexBinary"] = Node("VertexBinary", DfgVertices["Vertex"])
+DfgVertices["Vertex"].addSubClass(DfgVertices["VertexBinary"])
+DfgVertices["VertexTernary"] = Node("VertexTernary", DfgVertices["Vertex"])
+DfgVertices["Vertex"].addSubClass(DfgVertices["VertexTernary"])
+DfgVertices["VertexVariadic"] = Node("VertexVariadic", DfgVertices["Vertex"])
+DfgVertices["Vertex"].addSubClass(DfgVertices["VertexVariadic"])
+
+# Read DfgVertex definitions
+for filename in Args.dfgdef:
+    read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg")
+
+# Add the DfgVertex sub-types automatically derived from AstNode sub-types
+for node in AstNodeList:
+    # Ignore the hierarchy for now
+    if not node.isLeaf:
+        continue
+
+    # Ignore any explicitly defined vertex
+    if node.name in DfgVertices:
+        continue
+
+    if node.isSubClassOf(AstNodes["NodeUniop"]):
+        base = DfgVertices["VertexUnary"]
+    elif node.isSubClassOf(AstNodes["NodeBiop"]):
+        base = DfgVertices["VertexBinary"]
+    elif node.isSubClassOf(AstNodes["NodeTriop"]):
+        base = DfgVertices["VertexTernary"]
     else:
-        if not node.isLeaf:
-            sys.exit(
-                "%Error: Non-final AstNode subclasses must be named AstNode*: Ast"
-                + node.name)
+        continue
 
-# Check ordering of node definitions
-files = tuple(sorted(set(_.file for _ in SortedNodes)))
+    vertex = Node(node.name, base)
+    DfgVertices[node.name] = vertex
+    base.addSubClass(vertex)
 
-hasOrderingError = False
-for file in files:
-    nodes = tuple(filter(lambda _, f=file: _.file == f, SortedNodes))
-    expectOrder = tuple(sorted(nodes, key=lambda _: (_.isLeaf, _.ordIdx)))
-    actualOrder = tuple(sorted(nodes, key=lambda _: _.lineno))
-    expect = {
-        node: pred
-        for pred, node in zip((None, ) + expectOrder[:-1], expectOrder)
-    }
-    actual = {
-        node: pred
-        for pred, node in zip((None, ) + actualOrder[:-1], actualOrder)
-    }
-    for node in nodes:
-        if expect[node] != actual[node]:
-            hasOrderingError = True
-            pred = expect[node]
-            print(file + ":" + str(node.lineno) +
-                  ": %Error: Definition of 'Ast" + node.name +
-                  "' is out of order. Should be " +
-                  ("right after 'Ast" + pred.name +
-                   "'" if pred else "first in file") + ".",
-                  file=sys.stderr)
+    for n in range(1, node.arity + 1):
+        op = node.getOp(n)
+        if op is not None:
+            name, monad, kind = op
+            assert monad == "", "Cannot represnt AstNode as DfgVertex"
+            vertex.addOp(n, name, "", "")
 
-if hasOrderingError:
-    sys.exit("%Error: Stopping due to out of order definitions listed above")
+# Compute derived properties over the whole DfgVertex hierarchy
+DfgVertices["Vertex"].complete()
+
+DfgVertexList = tuple(map(lambda _: DfgVertices[_],
+                          sorted(DfgVertices.keys())))
+
+check_types(DfgVertexList, "Dfg", "Vertex")
+
+###############################################################################
+# Read additional files
+###############################################################################
 
 read_stages(Args.I + "/Verilator.cpp")
 
@@ -1081,16 +1326,31 @@ source_files.extend(glob.glob(Args.I + "/*.cpp"))
 for filename in source_files:
     read_refs(filename)
 
+###############################################################################
+# Generate output
+###############################################################################
+
 if Args.classes:
     write_report("V3Ast__gen_report.txt")
-    write_classes("V3Ast__gen_classes.h")
-    write_visitor_decls("V3Ast__gen_visitor_decls.h")
-    write_visitor_defns("V3Ast__gen_visitor_defns.h")
-    write_impl("V3Ast__gen_impl.h")
-    write_types("V3Ast__gen_types.h")
-    write_yystype("V3Ast__gen_yystype.h")
-    write_macros("V3Ast__gen_macros.h")
-    write_op_checks("V3Ast__gen_op_checks.h")
+    # Write Ast code
+    write_forward_class_decls("Ast", AstNodeList)
+    write_visitor_decls("Ast", AstNodeList)
+    write_visitor_defns("Ast", AstNodeList, "VNVisitor")
+    write_type_enum("Ast", AstNodeList)
+    write_type_tests("Ast", AstNodeList)
+    write_ast_macros("V3Ast__gen_macros.h")
+    write_ast_yystype("V3Ast__gen_yystype.h")
+    write_ast_op_checks("V3Ast__gen_op_checks.h")
+    # Write Dfg code
+    write_forward_class_decls("Dfg", DfgVertexList)
+    write_visitor_decls("Dfg", DfgVertexList)
+    write_visitor_defns("Dfg", DfgVertexList, "DfgVisitor")
+    write_type_enum("Dfg", DfgVertexList)
+    write_type_tests("Dfg", DfgVertexList)
+    write_dfg_macros("V3Dfg__gen_macros.h")
+    write_dfg_auto_classes("V3Dfg__gen_auto_classes.h")
+    write_dfg_ast_to_dfg("V3Dfg__gen_ast_to_dfg.h")
+    write_dfg_dfg_to_ast("V3Dfg__gen_dfg_to_ast.h")
 
 for cpt in Args.infiles:
     if not re.search(r'.cpp$', cpt):
diff --git a/src/config_build.h.in b/src/config_build.h.in
index d044c1411..32a0da56f 100644
--- a/src/config_build.h.in
+++ b/src/config_build.h.in
@@ -82,6 +82,7 @@ using std::endl;
 // - If defined, the default search path has it, so support is always enabled.
 // - If undef, not system-wide, user can set SYSTEMC_INCLUDE.
 #undef HAVE_SYSTEMC
+#undef HAVE_COROUTINES
 
 //**********************************************************************
 //**** OS and compiler specifics
diff --git a/src/verilog.l b/src/verilog.l
index 38adcdc84..b2cf6533f 100644
--- a/src/verilog.l
+++ b/src/verilog.l
@@ -131,6 +131,8 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
   "sc_bv"               { FL; return yVLT_SC_BV; }
   "sformat"             { FL; return yVLT_SFORMAT; }
   "split_var"           { FL; return yVLT_SPLIT_VAR; }
+  "timing_off"          { FL; return yVLT_TIMING_OFF; }
+  "timing_on"           { FL; return yVLT_TIMING_ON; }
   "tracing_off"         { FL; return yVLT_TRACING_OFF; }
   "tracing_on"          { FL; return yVLT_TRACING_ON; }
 
@@ -316,7 +318,7 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
   "endmodule"           { FL; return yENDMODULE; }
   "endprimitive"        { FL; return yENDPRIMITIVE; }
   "endspecify"          { FL; return yENDSPECIFY; }
-  "endtable"            { FL; yylval.fl->v3error("Syntax error: ENDTABLE outside of TABLE"); FL_BRK; }
+  "endtable"            { FL; yylval.fl->v3error("Syntax error: 'endtable' outside of 'table'"); FL_BRK; }
   "endtask"             { FL; return yENDTASK; }
   "event"               { FL; return yEVENT; }
   "for"                 { FL; return yFOR; }
@@ -748,6 +750,8 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
   "/*verilator split_var*/"             { FL; return yVL_SPLIT_VAR; }
   "/*verilator tag"[^*]*"*/"            { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext));
                                           return yVL_TAG; }
+  "/*verilator timing_off*/"            { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; }
+  "/*verilator timing_on*/"             { FL_FWD; PARSEP->lexFileline()->timingOn(true); FL_BRK; }
   "/*verilator trace_init_task*/"       { FL; return yVL_TRACE_INIT_TASK; }
   "/*verilator tracing_off*/"           { FL_FWD; PARSEP->lexFileline()->tracingOn(false); FL_BRK; }
   "/*verilator tracing_on*/"            { FL_FWD; PARSEP->lexFileline()->tracingOn(true); FL_BRK; }
@@ -957,7 +961,7 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
 "endtable"       { yy_pop_state(); FL; return yENDTABLE; }
 
"`line"{ws}+[^\n\r]*{crnl} { FL_FWD; PARSEP->lexPpline(yytext); FL_BRK; }
. { yymore(); } -
<> { FL; yylval.fl->v3error("EOF in TABLE"); +
<> { FL; yylval.fl->v3error("EOF in 'table'"); yyleng = 0; yy_pop_state(); FL_BRK; yyterminate(); } /************************************************************************/ @@ -1022,8 +1026,10 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "`begin_keywords"[ \t]*\"1800-2012\" { FL_FWD; yy_push_state(S12); PARSEP->lexPushKeywords(YY_START); FL_BRK; } "`begin_keywords"[ \t]*\"1800-2017\" { FL_FWD; yy_push_state(S17); PARSEP->lexPushKeywords(YY_START); FL_BRK; } "`begin_keywords"[ \t]*\"1800[+]VAMS\" { FL_FWD; yy_push_state(SAX); PARSEP->lexPushKeywords(YY_START); FL_BRK; } /*Latest SV*/ - "`end_keywords" { FL; yy_pop_state(); - if (!PARSEP->lexPopKeywords()) yylval.fl->v3error("`end_keywords when not inside `begin_keywords block"); + "`end_keywords" { FL; + if (!PARSEP->lexPopKeywords()) { + yylval.fl->v3error("`end_keywords when not inside `begin_keywords block"); + } else { yy_pop_state(); } FL_BRK; } /* Verilator */ @@ -1068,8 +1074,9 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} } /* Catch all - absolutely last */ -<*>.|\n { FL; yylval.fl->v3error("Missing verilog.l rule: Default rule invoked in state " - << YY_START << " '" << yytext << "'"); +<*>.|\n { FL; yylval.fl->v3error( // LCOV_EXCL_LINE + "Missing verilog.l rule: Default rule invoked in state " + << YY_START << " '" << yytext << "'"); FL_BRK; } %% // Avoid code here as cl format misindents diff --git a/src/verilog.y b/src/verilog.y index 674f2a3a2..c73dad6e4 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -40,6 +40,24 @@ #define BBUNSUP(fl, msg) (fl)->v3warn(E_UNSUPPORTED, msg) #define GATEUNSUP(fl, tok) \ { BBUNSUP((fl), "Unsupported: Verilog 1995 gate primitive: " << (tok)); } +#define RISEFALLDLYUNSUP(nodep) \ + if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \ + nodep->v3warn(RISEFALLDLY, \ + "Unsupported: rising/falling/turn-off delays. Using the first delay"); \ + } +#define MINTYPMAXDLYUNSUP(nodep) \ + if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \ + nodep->v3warn( \ + MINTYPMAXDLY, \ + "Unsupported: minimum/typical/maximum delay expressions. Using the typical delay"); \ + } +#define PUT_DLYS_IN_ASSIGNS(delayp, assignsp) \ + if (delayp) { \ + for (auto* nodep = assignsp; nodep; nodep = nodep->nextp()) { \ + auto* const assignp = VN_AS(nodep, NodeAssign); \ + assignp->timingControlp(nodep == assignsp ? delayp : delayp->cloneTree(false)); \ + } \ + } #define STRENGTHUNSUP(nodep) \ { \ if (nodep) { \ @@ -47,13 +65,6 @@ nodep->deleteTree(); \ } \ } -#define PRIMDLYUNSUP(nodep) \ - { \ - if (nodep) { \ - nodep->v3warn(ASSIGNDLY, "Unsupported: Ignoring delay on this primitive."); \ - nodep->deleteTree(); \ - } \ - } //====================================================================== // Statics (for here only) @@ -74,7 +85,7 @@ public: AstCase* m_caseAttrp = nullptr; // Current case statement for attribute adding AstNodeDType* m_varDTypep = nullptr; // Pointer to data type for next signal declaration AstNodeDType* m_memDTypep = nullptr; // Pointer to data type for next member declaration - AstNode* m_netDelayp = nullptr; // Pointer to delay for next signal declaration + AstDelay* m_netDelayp = nullptr; // Pointer to delay for next signal declaration AstStrengthSpec* m_netStrengthp = nullptr; // Pointer to strength for next net declaration AstNodeModule* m_modp = nullptr; // Last module for timeunits bool m_pinAnsi = false; // In ANSI port list @@ -179,7 +190,7 @@ public: if (m_varDTypep) VL_DO_CLEAR(m_varDTypep->deleteTree(), m_varDTypep = nullptr); m_varDTypep = dtypep; } - void setNetDelay(AstNode* netDelayp) { m_netDelayp = netDelayp; } + void setNetDelay(AstDelay* netDelayp) { m_netDelayp = netDelayp; } void setNetStrength(AstStrengthSpec* netStrengthp) { m_netStrengthp = netStrengthp; } void pinPush() { m_pinStack.push(m_pinNum); @@ -418,6 +429,8 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_SC_BV "sc_bv" %token yVLT_SFORMAT "sformat" %token yVLT_SPLIT_VAR "split_var" +%token yVLT_TIMING_OFF "timing_off" +%token yVLT_TIMING_ON "timing_on" %token yVLT_TRACING_OFF "tracing_off" %token yVLT_TRACING_ON "tracing_on" @@ -1069,6 +1082,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) // Blank lines for type insertion // Blank lines for type insertion // Blank lines for type insertion +// Blank lines for type insertion %start source_text @@ -1388,9 +1402,9 @@ port: // ==IEEE: port VARDTYPE(new AstIfaceRefDType($2, $4, "", *$2, *$4)); addNextNull($$, VARDONEP($$,$6,$7)); } | portDirNetE yINTERFACE portSig rangeListE sigAttrListE - { $$ = nullptr; BBUNSUP($2, "Unsupported: virtual or generic interfaces"); } + { $$ = nullptr; BBUNSUP($2, "Unsupported: generic interfaces"); } | portDirNetE yINTERFACE '.' idAny/*modport*/ portSig rangeListE sigAttrListE - { $$ = nullptr; BBUNSUP($2, "Unsupported: virtual or generic interfaces"); } + { $$ = nullptr; BBUNSUP($2, "Unsupported: generic interfaces"); } // // // IEEE: ansi_port_declaration, with [port_direction] removed // // IEEE: [ net_port_header | interface_port_header ] @@ -1631,6 +1645,8 @@ program_generate_item: // ==IEEE: program_generate_item loop_generate_construct { $$ = $1; } | conditional_generate_construct { $$ = $1; } | generate_region { $$ = $1; } + // not in IEEE, but presumed so can do yBEGIN ... yEND + | genItemBegin { $$ = $1; } | elaboration_system_task { $$ = $1; } ; @@ -1783,13 +1799,13 @@ net_dataTypeE: // // Otherwise #(...) can't be determined to be a delay or parameters // // Submit this as a footnote to the committee var_data_type { $$ = $1; } - | signingE rangeList delayE + | signingE rangeList delay_controlE { $$ = GRAMMARP->addRange(new AstBasicDType{$2->fileline(), LOGIC, $1}, $2, true); GRAMMARP->setNetDelay($3); } // not implicit | signing { $$ = new AstBasicDType{$1, LOGIC, $1}; } // not implicit - | /*implicit*/ delayE + | /*implicit*/ delay_controlE { $$ = new AstBasicDType{CRELINE(), LOGIC}; GRAMMARP->setNetDelay($1); } // not implicit ; @@ -1961,24 +1977,31 @@ data_typeNoRef: // ==IEEE: data_type, excluding class_ty { $$ = new AstDefImplicitDType{$1->fileline(), "__typeimpenum" + cvtToStr(GRAMMARP->s_modTypeImpNum++), SYMP, VFlagChildDType{}, $1}; } - | ySTRING { $$ = new AstBasicDType($1,VBasicDTypeKwd::STRING); } - | yCHANDLE { $$ = new AstBasicDType($1,VBasicDTypeKwd::CHANDLE); } - | yEVENT { $$ = new AstBasicDType($1,VBasicDTypeKwd::EVENTVALUE); } - // // Rules overlap virtual_interface_declaration - // // Parameters here are SV2009 - // // IEEE has ['.' modport] but that will conflict with port - // // declarations which decode '.' modport themselves, so - // // instead see data_typeVar - | yVIRTUAL__INTERFACE yINTERFACE id/*interface*/ - { $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; - BBUNSUP($1, "Unsupported: virtual interface"); } - | yVIRTUAL__anyID id/*interface*/ - { $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; - BBUNSUP($1, "Unsupported: virtual data type"); } + | ySTRING + { $$ = new AstBasicDType{$1, VBasicDTypeKwd::STRING}; } + | yCHANDLE + { $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; } + | yEVENT + { $$ = new AstBasicDType{$1, VBasicDTypeKwd::EVENT}; v3Global.setHasEvents(); } | type_reference { $$ = $1; } // // IEEE: class_scope: see data_type above // // IEEE: class_type: see data_type above // // IEEE: ps_covergroup: see data_type above + // // Rules overlap virtual_interface_declaration + | yVIRTUAL__INTERFACE yINTERFACE data_typeVirtual + { $$ = $3; } + | yVIRTUAL__anyID data_typeVirtual + { $$ = $2; } + ; + +data_typeVirtual: // ==IEEE: data_type after yVIRTUAL [ yINTERFACE ] + // // Parameters here are SV2009 + id/*interface*/ { $$ = new AstIfaceRefDType{$1, "", *$1}; } + | id/*interface*/ '.' id/*modport*/ { $$ = new AstIfaceRefDType{$1, $3, "", *$1, *$3}; } + | id/*interface*/ parameter_value_assignmentClass + { $$ = new AstIfaceRefDType{$1, nullptr, "", *$1, "", $2}; } + | id/*interface*/ parameter_value_assignmentClass '.' id/*modport*/ + { $$ = new AstIfaceRefDType{$1, $4, "", *$1, *$4, $2}; } ; data_type_or_void: // ==IEEE: data_type_or_void @@ -2403,6 +2426,8 @@ module_item: // ==IEEE: module_item non_port_module_item: // ==IEEE: non_port_module_item generate_region { $$ = $1; } + // not in IEEE, but presumed so can do yBEGIN ... yEND + | genItemBegin { $$ = $1; } | module_or_generate_item { $$ = $1; } | specify_block { $$ = $1; } | specparam_declaration { $$ = $1; } @@ -2467,15 +2492,11 @@ module_common_item: // ==IEEE: module_common_item ; continuous_assign: // IEEE: continuous_assign - yASSIGN driveStrengthE delayE assignList ';' + yASSIGN driveStrengthE delay_controlE assignList ';' { $$ = $4; - APPLY_STRENGTH_TO_LIST($$, $2, AssignW); - if ($3) - for (auto* nodep = $$; nodep; nodep = nodep->nextp()) { - auto* const assignp = VN_AS(nodep, NodeAssign); - assignp->timingControlp(nodep == $$ ? $3 : $3->cloneTree(false)); - } + APPLY_STRENGTH_TO_LIST($4, $2, AssignW); + PUT_DLYS_IN_ASSIGNS($3, $4); } ; @@ -2718,27 +2739,26 @@ assignOne: ; delay_or_event_controlE: // IEEE: delay_or_event_control plus empty + /* empty */ { $$ = nullptr; } + | delay_control { $$ = $1; } + | event_control { $$ = $1; } +//UNSUP | yREPEAT '(' expr ')' event_control { } + ; + +delay_controlE: /* empty */ { $$ = nullptr; } | delay_control { $$ = $1; } - | event_control { $$ = $1; } -//UNSUP | yREPEAT '(' expr ')' event_control { } ; -delayE: - /* empty */ { $$ = nullptr; } - | delay { $$ = $1; } - ; - -delay: - delay_control - { $$ = $1; } - ; - -delay_control: //== IEEE: delay_control - '#' delay_value { $$ = $2; } - | '#' '(' minTypMax ')' { $$ = $3; } - | '#' '(' minTypMax ',' minTypMax ')' { $$ = $3; DEL($5); } - | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' { $$ = $3; DEL($5); DEL($7); } +delay_control: //== IEEE: delay_control + '#' delay_value + { $$ = new AstDelay{$1, $2}; } + | '#' '(' minTypMax ')' + { $$ = new AstDelay{$1, $3}; } + | '#' '(' minTypMax ',' minTypMax ')' + { $$ = new AstDelay{$1, $3}; RISEFALLDLYUNSUP($3); DEL($5); } + | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' + { $$ = new AstDelay{$1, $3}; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); } ; delay_value: // ==IEEE:delay_value @@ -2755,7 +2775,7 @@ delayExpr: minTypMax: // IEEE: mintypmax_expression and constant_mintypmax_expression delayExpr { $$ = $1; } - | delayExpr ':' delayExpr ':' delayExpr { $$ = $3; DEL($1); DEL($5); } + | delayExpr ':' delayExpr ':' delayExpr { $$ = $3; MINTYPMAXDLYUNSUP($3); DEL($1); DEL($5); } ; netSigList: // IEEE: list_of_port_identifiers @@ -2770,8 +2790,8 @@ netSig: // IEEE: net_decl_assignment - one element from { $$ = VARDONEA($1, *$1, nullptr, $2); auto* const assignp = new AstAssignW{$3, new AstVarRef{$1, *$1, VAccess::WRITE}, $4}; if (GRAMMARP->m_netStrengthp) assignp->strengthSpecp(GRAMMARP->m_netStrengthp->cloneTree(false)); - if ($$->delayp()) assignp->timingControlp($$->delayp()->unlinkFrBack()); // IEEE 1800-2017 10.3.3 - AstNode::addNext($$, assignp); } | netId variable_dimensionList sigAttrListE + AstNode::addNext($$, assignp); } + | netId variable_dimensionList sigAttrListE { $$ = VARDONEA($1,*$1, $2, $3); } ; @@ -2870,7 +2890,9 @@ list_of_param_assignments: // ==IEEE: list_of_param_assignments type_assignment: // ==IEEE: type_assignment // // note exptOrDataType being a data_type is only for yPARAMETER yTYPE - idAny/*new-parameter*/ sigAttrListE '=' data_type + idAny/*new-parameter*/ sigAttrListE + { $$ = VARDONEA($1, *$1, nullptr, $2); } + | idAny/*new-parameter*/ sigAttrListE '=' data_type { $$ = VARDONEA($1, *$1, nullptr, $2); $$->valuep($4); } ; @@ -3068,35 +3090,20 @@ event_expression: // IEEE: event_expression - split over several senitem: // IEEE: part of event_expression, non-'OR' ',' terms senitemEdge { $$ = $1; } - | senitemVar { $$ = $1; } - | '(' senitem ')' { $$ = $2; } - //UNSUP expr { UNSUP } - | '{' event_expression '}' { $$ = $2; } - | senitem yP_ANDAND senitem { $$ = new AstSenItem($2, AstSenItem::Illegal()); } + | expr { $$ = new AstSenItem{$1, VEdgeType::ET_CHANGED, $1}; } //UNSUP expr yIFF expr { UNSUP } - // Since expr is unsupported we allow and ignore constants (removed in V3Const) - | yaINTNUM { $$ = nullptr; } - | yaFLOATNUM { $$ = nullptr; } ; senitemVar: - idClassSel { $$ = new AstSenItem($1->fileline(), VEdgeType::ET_ANYEDGE, $1); } + idClassSel { $$ = new AstSenItem{$1->fileline(), VEdgeType::ET_CHANGED, $1}; } ; senitemEdge: // IEEE: part of event_expression - //UNSUP // Below are all removed - yPOSEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_POSEDGE, $2); } - | yNEGEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_NEGEDGE, $2); } - | yEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_BOTHEDGE, $2); } - | yPOSEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_POSEDGE, $3); } - | yNEGEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_NEGEDGE, $3); } - | yEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_BOTHEDGE, $3); } - //UNSUP // Above are all removed, replace with: - //UNSUP yPOSEDGE expr { UNSUP } + yPOSEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_POSEDGE, $2}; } + | yNEGEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_NEGEDGE, $2}; } + | yEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_BOTHEDGE, $2}; } //UNSUP yPOSEDGE expr yIFF expr { UNSUP } - //UNSUP yNEGEDGE expr { UNSUP } //UNSUP yNEGEDGE expr yIFF expr { UNSUP } - //UNSUP yEDGE expr { UNSUP } //UNSUP yEDGE expr yIFF expr { UNSUP } ; @@ -3300,11 +3307,9 @@ statement_item: // IEEE: statement_item | yDISABLE yFORK ';' { $$ = new AstDisableFork($1); } // // IEEE: event_trigger | yP_MINUSGT idDotted/*hierarchical_identifier-event*/ ';' - { // AssignDly because we don't have stratified queue, and need to - // read events, clear next event, THEN apply this set - $$ = new AstAssignDly($1, $2, new AstConst($1, AstConst::BitTrue())); } + { $$ = new AstFireEvent{$1, $2, false}; } | yP_MINUSGTGT delay_or_event_controlE idDotted/*hierarchical_identifier-event*/ ';' - { $$ = new AstAssignDly{$1, $3, new AstConst{$1, AstConst::BitTrue()}, $2}; } + { $$ = new AstFireEvent{$1, $3, true}; } // // // IEEE: loop_statement | yFOREVER stmtBlock { $$ = new AstWhile($1,new AstConst($1, AstConst::BitTrue()), $2); } @@ -3328,8 +3333,17 @@ statement_item: // IEEE: statement_item // | par_block { $$ = $1; } // // IEEE: procedural_timing_control_statement + procedural_timing_control - | delay_control stmtBlock { $$ = new AstDelay{$1->fileline(), $1, $2}; } - | event_control stmtBlock { $$ = new AstEventControl(FILELINE_OR_CRE($1), $1, $2); } + | delay_control stmtBlock { AstNode* nextp = nullptr; + if ($2) { + if ($2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext(); + $1->addStmtsp($2); + } + $$ = $1; + addNextNull($$, nextp); } + | event_control stmtBlock { AstNode* nextp = nullptr; + if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext(); + $$ = new AstEventControl{FILELINE_OR_CRE($1), $1, $2}; + addNextNull($$, nextp); } //UNSUP cycle_delay stmtBlock { UNSUP } // | seq_block { $$ = $1; } @@ -4763,45 +4777,33 @@ stream_expressionOrDataType: // IEEE: from streaming_concatenation // Gate declarations gateDecl: - yBUF driveStrengthE delayE gateBufList ';' - { $$ = $4; STRENGTHUNSUP($2); PRIMDLYUNSUP($3); } - | yBUFIF0 driveStrengthE delayE gateBufif0List ';' - { $$ = $4; STRENGTHUNSUP($2); PRIMDLYUNSUP($3); } - | yBUFIF1 driveStrengthE delayE gateBufif1List ';' - { $$ = $4; STRENGTHUNSUP($2); PRIMDLYUNSUP($3); } - | yNOT driveStrengthE delayE gateNotList ';' - { $$ = $4; APPLY_STRENGTH_TO_LIST($$, $2, AssignW); PRIMDLYUNSUP($3); } - | yNOTIF0 driveStrengthE delayE gateNotif0List ';' - { $$ = $4; STRENGTHUNSUP($2); PRIMDLYUNSUP($3); } - | yNOTIF1 driveStrengthE delayE gateNotif1List ';' - { $$ = $4; STRENGTHUNSUP($2); PRIMDLYUNSUP($3); } - | yAND driveStrengthE delayE gateAndList ';' - { $$ = $4; APPLY_STRENGTH_TO_LIST($$, $2, AssignW); PRIMDLYUNSUP($3); } - | yNAND driveStrengthE delayE gateNandList ';' - { $$ = $4; APPLY_STRENGTH_TO_LIST($$, $2, AssignW); PRIMDLYUNSUP($3); } - | yOR driveStrengthE delayE gateOrList ';' - { $$ = $4; APPLY_STRENGTH_TO_LIST($$, $2, AssignW); PRIMDLYUNSUP($3); } - | yNOR driveStrengthE delayE gateNorList ';' - { $$ = $4; APPLY_STRENGTH_TO_LIST($$, $2, AssignW); PRIMDLYUNSUP($3); } - | yXOR driveStrengthE delayE gateXorList ';' - { $$ = $4; APPLY_STRENGTH_TO_LIST($$, $2, AssignW); PRIMDLYUNSUP($3); } - | yXNOR driveStrengthE delayE gateXnorList ';' - { $$ = $4; APPLY_STRENGTH_TO_LIST($$, $2, AssignW); PRIMDLYUNSUP($3); } - | yPULLUP delayE gatePullupList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yPULLDOWN delayE gatePulldownList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yNMOS delayE gateBufif1List ';' { $$ = $3; PRIMDLYUNSUP($2); } // ~=bufif1, as don't have strengths yet - | yPMOS delayE gateBufif0List ';' { $$ = $3; PRIMDLYUNSUP($2); } // ~=bufif0, as don't have strengths yet + yBUF driveStrengthE delay_controlE gateBufList ';' { $$ = $4; STRENGTHUNSUP($2); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yBUFIF0 driveStrengthE delay_controlE gateBufif0List ';' { $$ = $4; STRENGTHUNSUP($2); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yBUFIF1 driveStrengthE delay_controlE gateBufif1List ';' { $$ = $4; STRENGTHUNSUP($2); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yNOT driveStrengthE delay_controlE gateNotList ';' { $$ = $4; APPLY_STRENGTH_TO_LIST($4, $2, AssignW); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yNOTIF0 driveStrengthE delay_controlE gateNotif0List ';' { $$ = $4; STRENGTHUNSUP($2); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yNOTIF1 driveStrengthE delay_controlE gateNotif1List ';' { $$ = $4; STRENGTHUNSUP($2); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yAND driveStrengthE delay_controlE gateAndList ';' { $$ = $4; APPLY_STRENGTH_TO_LIST($4, $2, AssignW); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yNAND driveStrengthE delay_controlE gateNandList ';' { $$ = $4; APPLY_STRENGTH_TO_LIST($4, $2, AssignW); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yOR driveStrengthE delay_controlE gateOrList ';' { $$ = $4; APPLY_STRENGTH_TO_LIST($4, $2, AssignW); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yNOR driveStrengthE delay_controlE gateNorList ';' { $$ = $4; APPLY_STRENGTH_TO_LIST($4, $2, AssignW); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yXOR driveStrengthE delay_controlE gateXorList ';' { $$ = $4; APPLY_STRENGTH_TO_LIST($4, $2, AssignW); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yXNOR driveStrengthE delay_controlE gateXnorList ';' { $$ = $4; APPLY_STRENGTH_TO_LIST($4, $2, AssignW); PUT_DLYS_IN_ASSIGNS($3, $4); } + | yPULLUP delay_controlE gatePullupList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yPULLDOWN delay_controlE gatePulldownList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yNMOS delay_controlE gateBufif1List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yPMOS delay_controlE gateBufif0List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } // - | yTRAN delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tran"); } // Unsupported - | yRCMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rcmos"); } // Unsupported - | yCMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"cmos"); } // Unsupported - | yRNMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rmos"); } // Unsupported - | yRPMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"pmos"); } // Unsupported - | yRTRAN delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtran"); } // Unsupported - | yRTRANIF0 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif0"); } // Unsupported - | yRTRANIF1 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif1"); } // Unsupported - | yTRANIF0 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif0"); } // Unsupported - | yTRANIF1 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif1"); } // Unsupported + | yTRAN delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tran"); } // Unsupported + | yRCMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rcmos"); } // Unsupported + | yCMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"cmos"); } // Unsupported + | yRNMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rmos"); } // Unsupported + | yRPMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"pmos"); } // Unsupported + | yRTRAN delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtran"); } // Unsupported + | yRTRANIF0 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif0"); } // Unsupported + | yRTRANIF1 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif1"); } // Unsupported + | yTRANIF0 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif0"); } // Unsupported + | yTRANIF1 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif1"); } // Unsupported ; gateBufList: @@ -5385,10 +5387,12 @@ concurrent_assertion_item: // IEEE: concurrent_assertion_item concurrent_assertion_statement: // ==IEEE: concurrent_assertion_statement // // IEEE: assert_property_statement //UNSUP remove below: - yASSERT yPROPERTY '(' property_spec ')' elseStmtBlock { $$ = new AstAssert($1, $4, nullptr, $6, false); } + yASSERT yPROPERTY '(' property_spec ')' elseStmtBlock + { $$ = new AstAssert{$1, new AstSampled{$1, $4}, nullptr, $6, false}; } //UNSUP yASSERT yPROPERTY '(' property_spec ')' action_block { } // // IEEE: assume_property_statement - | yASSUME yPROPERTY '(' property_spec ')' elseStmtBlock { $$ = new AstAssert($1, $4, nullptr, $6, false); } + | yASSUME yPROPERTY '(' property_spec ')' elseStmtBlock + { $$ = new AstAssert{$1, new AstSampled{$1, $4}, nullptr, $6, false}; } //UNSUP yASSUME yPROPERTY '(' property_spec ')' action_block { } // // IEEE: cover_property_statement | yCOVER yPROPERTY '(' property_spec ')' stmtBlock { $$ = new AstCover($1, $4, $6, false); } @@ -6215,7 +6219,8 @@ classFront: // IEEE: part of class_declaration { $$ = new AstClass($2, *$4); $$->isVirtual($1); $$->lifetime($3); - SYMP->pushNew($$); } + SYMP->pushNew($$); + v3Global.setHasClasses(); } // // IEEE: part of interface_class_declaration | yINTERFACE yCLASS lifetimeE idAny/*class_identifier*/ { $$ = new AstClass($2, *$4); @@ -6638,19 +6643,25 @@ vltItem: vltOffFront: yVLT_COVERAGE_OFF { $$ = V3ErrorCode::I_COVERAGE; } + | yVLT_TIMING_OFF { $$ = V3ErrorCode::I_TIMING; } | yVLT_TRACING_OFF { $$ = V3ErrorCode::I_TRACING; } | yVLT_LINT_OFF { $$ = V3ErrorCode::I_LINT; } | yVLT_LINT_OFF yVLT_D_RULE idAny - { $$ = V3ErrorCode{(*$3).c_str()}; + { const char *codemsg = (*$3).c_str(); + if (V3ErrorCode::unusedMsg(codemsg)) {$$ = V3ErrorCode::I_UNUSED; } + else {$$ = V3ErrorCode{codemsg}; } if ($$ == V3ErrorCode::EC_ERROR) { $1->v3error("Unknown Error Code: " << *$3); } } ; vltOnFront: yVLT_COVERAGE_ON { $$ = V3ErrorCode::I_COVERAGE; } + | yVLT_TIMING_ON { $$ = V3ErrorCode::I_TIMING; } | yVLT_TRACING_ON { $$ = V3ErrorCode::I_TRACING; } | yVLT_LINT_ON { $$ = V3ErrorCode::I_LINT; } | yVLT_LINT_ON yVLT_D_RULE idAny - { $$ = V3ErrorCode{(*$3).c_str()}; + { const char *codemsg = (*$3).c_str(); + if (V3ErrorCode::unusedMsg(codemsg)) {$$ = V3ErrorCode::I_UNUSED; } + else {$$ = V3ErrorCode{codemsg}; } if ($$ == V3ErrorCode::EC_ERROR) { $1->v3error("Unknown Error Code: " << *$3); } } ; diff --git a/test_regress/driver.pl b/test_regress/driver.pl index da738d16a..4bd739304 100755 --- a/test_regress/driver.pl +++ b/test_regress/driver.pl @@ -1125,7 +1125,7 @@ sub compile { } if (!$param{fails} && $param{make_main}) { - $self->_make_main(); + $self->_make_main($param{timing_loop}); } if ($param{verilator_make_gmake} @@ -1476,7 +1476,13 @@ sub sc { sub have_sc { my $self = (ref $_[0] ? shift : $Self); return 1 if (defined $ENV{SYSTEMC} || defined $ENV{SYSTEMC_INCLUDE} || $ENV{CFG_HAVE_SYSTEMC}); - return 1 if $self->verilator_version =~ /systemc found *= *1/i; + return 1 if $self->verilator_get_supported('SYSTEMC'); + return 0; +} + +sub have_coroutines { + my $self = (ref $_[0] ? shift : $Self); + return 1 if $self->verilator_get_supported('COROUTINES'); return 0; } @@ -1731,6 +1737,11 @@ sub _try_regex { sub _make_main { my $self = shift; + my $timing_loop = shift; + + if ($timing_loop && $self->sc) { + $self->error("Cannot use timing loop and SystemC together!\n"); + } if ($self->vhdl) { $self->_read_inputs_vhdl(); @@ -1859,33 +1870,61 @@ sub _make_main { } print $fh " ${set}fastclk = false;\n" if $self->{inputs}{fastclk}; print $fh " ${set}clk = false;\n" if $self->{inputs}{clk}; - _print_advance_time($self, $fh, 10); + if (!$timing_loop) { + _print_advance_time($self, $fh, 10); + } print $fh " }\n"; my $time = $self->sc ? "sc_time_stamp()" : "contextp->time()"; - print $fh " while ((${time} < sim_time * MAIN_TIME_MULTIPLIER)\n"; - print $fh " && !contextp->gotFinish()) {\n"; + print $fh " while ("; + if (!$timing_loop || $self->{inputs}{clk}) { + print $fh "(${time} < sim_time * MAIN_TIME_MULTIPLIER) && "; + } + print $fh "!contextp->gotFinish()) {\n"; - for (my $i = 0; $i < 5; $i++) { - my $action = 0; - if ($self->{inputs}{fastclk}) { - print $fh " ${set}fastclk = !${set}fastclk;\n"; - $action = 1; + if ($timing_loop) { + print $fh " topp->eval();\n"; + if ($self->{trace}) { + $fh->print("#if VM_TRACE\n"); + $fh->print(" if (tfp) tfp->dump(contextp->time());\n"); + $fh->print("#endif // VM_TRACE\n"); } - if ($i == 0 && $self->{inputs}{clk}) { - print $fh " ${set}clk = !${set}clk;\n"; - $action = 1; + if ($self->{inputs}{clk}) { + print $fh " uint64_t cycles = contextp->time() / MAIN_TIME_MULTIPLIER;\n"; + print $fh " uint64_t new_time = (cycles + 1) * MAIN_TIME_MULTIPLIER;\n"; + print $fh " if (topp->eventsPending() &&\n"; + print $fh " topp->nextTimeSlot() / MAIN_TIME_MULTIPLIER <= cycles) {\n"; + print $fh " new_time = topp->nextTimeSlot();\n"; + print $fh " } else {\n"; + print $fh " ${set}clk = !${set}clk;\n"; + print $fh " }\n"; + print $fh " contextp->time(new_time);\n"; + } else { + print $fh " if (!topp->eventsPending()) break;\n"; + print $fh " contextp->time(topp->nextTimeSlot());\n"; } - if ($self->{savable}) { - $fh->print(" if (save_time && ${time} == save_time) {\n"); - $fh->print(" save_model(\"$self->{obj_dir}/saved.vltsv\");\n"); - $fh->print(" printf(\"Exiting after save_model\\n\");\n"); - $fh->print(" topp.reset(nullptr);\n"); - $fh->print(" return 0;\n"); - $fh->print(" }\n"); + } else { + for (my $i = 0; $i < 5; $i++) { + my $action = 0; + if ($self->{inputs}{fastclk}) { + print $fh " ${set}fastclk = !${set}fastclk;\n"; + $action = 1; + } + if ($i == 0 && $self->{inputs}{clk}) { + print $fh " ${set}clk = !${set}clk;\n"; + $action = 1; + } + if ($self->{savable}) { + $fh->print(" if (save_time && ${time} == save_time) {\n"); + $fh->print(" save_model(\"$self->{obj_dir}/saved.vltsv\");\n"); + $fh->print(" printf(\"Exiting after save_model\\n\");\n"); + $fh->print(" topp.reset(nullptr);\n"); + $fh->print(" return 0;\n"); + $fh->print(" }\n"); + } + _print_advance_time($self, $fh, 1, $action); } - _print_advance_time($self, $fh, 1, $action); } if ($self->{benchmarksim}) { $fh->print(" if (VL_UNLIKELY(!warm)) {\n"); @@ -1941,7 +1980,7 @@ sub _print_advance_time { else { $set = "topp->"; } if ($self->sc) { - print $fh " sc_start(${time}, $Self->{sc_time_resolution});\n"; + print $fh " sc_start(${time} * MAIN_TIME_MULTIPLIER, $Self->{sc_time_resolution});\n"; } else { if ($action) { print $fh " ${set}eval();\n"; @@ -2123,17 +2162,20 @@ sub _read_inputs_vhdl { ####################################################################### # Verilator utilities -our $_Verilator_Version; -sub verilator_version { - # Returns verbose version, line 1 contains actual version - if (!defined $_Verilator_Version) { - my @args = ("perl", "$ENV{VERILATOR_ROOT}/bin/verilator", "-V"); +our %_Verilator_Supported; +sub verilator_get_supported { + my $self = (ref $_[0] ? shift : $Self); + my $feature = shift; + # Returns if given feature is supported + if (!defined $_Verilator_Supported{$feature}) { + my @args = ("perl", "$ENV{VERILATOR_ROOT}/bin/verilator", "-get-supported", $feature); my $args = join(' ', @args); - $_Verilator_Version = `$args`; - $_Verilator_Version or die "can't fork: $! " . join(' ', @args); - chomp $_Verilator_Version; + my $out = `$args`; + $out or die "couldn't run: $! " . join(' ', @args); + chomp $out; + $_Verilator_Supported{$feature} = ($out =~ /1/ ? 1 : 0); } - return $_Verilator_Version if defined $_Verilator_Version; + return $_Verilator_Supported{$feature}; } ####################################################################### diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 44676b6dc..0a3671e49 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -46,6 +46,7 @@ //END_MODULE_NAME-------------------------------------------------------------- //See also: https://github.com/twosigma/verilator_support +// verilator lint_off BLKANDNBLK // verilator lint_off COMBDLY // verilator lint_off INITIALDLY // verilator lint_off MULTIDRIVEN diff --git a/test_regress/t/t_altera_lpm_counter.pl b/test_regress/t/t_altera_lpm_counter.pl index e079ef062..78ec8c557 100755 --- a/test_regress/t/t_altera_lpm_counter.pl +++ b/test_regress/t/t_altera_lpm_counter.pl @@ -14,7 +14,7 @@ top_filename("t/t_altera_lpm.v"); (my $module = $Self->{name}) =~ s/.*t_altera_//; compile( - verilator_flags2 => ["--top-module ${module}"] + verilator_flags2 => ["--top-module ${module}", "--no-timing"] ); ok(1); diff --git a/test_regress/t/t_alw_dly.pl b/test_regress/t/t_alw_dly.pl index 102ff669d..b46d46042 100755 --- a/test_regress/t/t_alw_dly.pl +++ b/test_regress/t/t_alw_dly.pl @@ -11,7 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA"], ); execute( diff --git a/test_regress/t/t_assert_past.pl b/test_regress/t/t_assert_past.pl new file mode 100755 index 000000000..c505d6263 --- /dev/null +++ b/test_regress/t/t_assert_past.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ['--assert'], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assert_past.v b/test_regress/t/t_assert_past.v new file mode 100644 index 000000000..df6c6a920 --- /dev/null +++ b/test_regress/t/t_assert_past.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + clk + ); + input clk; + int cyc = 0; + logic val = 0; + // Example: + always @(posedge clk) begin + cyc <= cyc + 1; + val = ~val; + $display("t=%0t cyc=%0d val=%b", $time, cyc, val); + if (cyc == 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + assert property(@(posedge clk) cyc % 2 == 0 |=> $past(val) == 0) + else $display("$past assert 1 failed"); + assert property(@(posedge clk) cyc % 2 == 1 |=> $past(val) == 1) + else $display("$past assert 2 failed"); + // Example end +endmodule diff --git a/test_regress/t/t_assert_sampled.pl b/test_regress/t/t_assert_sampled.pl new file mode 100755 index 000000000..c505d6263 --- /dev/null +++ b/test_regress/t/t_assert_sampled.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ['--assert'], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assert_sampled.v b/test_regress/t/t_assert_sampled.v new file mode 100644 index 000000000..49c7cc71e --- /dev/null +++ b/test_regress/t/t_assert_sampled.v @@ -0,0 +1,56 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg [3:0] a, b; + + Test1 t1(clk, a, b); + Test2 t2(clk, a, b); + + initial begin + a = 0; + b = 0; + end + + always @(posedge clk) begin + a <= a + 1; + b = b + 1; + + $display("a = %0d, b = %0d, %0d == %0d", a, b, $sampled(a), $sampled(b)); + + if (b >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module Test1( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) $sampled(a) == $sampled(b)); + +endmodule + +module Test2( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) a == b); + +endmodule diff --git a/test_regress/t/t_assoc2.v b/test_regress/t/t_assoc2.v index 8cea07e6b..5b94ca3b9 100644 --- a/test_regress/t/t_assoc2.v +++ b/test_regress/t/t_assoc2.v @@ -16,18 +16,30 @@ module t (/*AUTOARG*/ integer cyc = 0; + int imap[int]; + // associative array of an associative array logic [31:0] a [logic [31:0]][logic [63:0]]; + task disp(); + int i = 60; + imap[i++] = 600; + imap[i++] = 601; + foreach (imap[k]) $display("imap[%0d] = %0d", k, imap[k]); + endtask + always @ (posedge clk) begin cyc <= cyc + 1; if (cyc == 1) begin a[5][8] = 8; a[5][9] = 9; + imap[10] = 100; + imap[11] = 101; end else if (cyc == 2) begin `checkh(a[5][8], 8); `checkh(a[5][9], 9); + disp(); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_assoc_method_bad.out b/test_regress/t/t_assoc_method_bad.out index 6c0ff94cc..76c49e771 100644 --- a/test_regress/t/t_assoc_method_bad.out +++ b/test_regress/t/t_assoc_method_bad.out @@ -50,4 +50,8 @@ : ... In instance t 27 | a.shuffle; | ^~~~~~~ +%Error: t/t_assoc_method_bad.v:29:9: Unknown built-in associative array method 'bad_not_defined' + : ... In instance t + 29 | a.bad_not_defined(); + | ^~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assoc_method_bad.v b/test_regress/t/t_assoc_method_bad.v index bd4270f7f..9921ef6bd 100644 --- a/test_regress/t/t_assoc_method_bad.v +++ b/test_regress/t/t_assoc_method_bad.v @@ -25,5 +25,7 @@ module t (/*AUTOARG*/); a.rsort; // Not legal on assoc a.reverse; // Not legal on assoc a.shuffle; // Not legal on assoc + + a.bad_not_defined(); end endmodule diff --git a/test_regress/t/t_assoc_wildcard_bad.out b/test_regress/t/t_assoc_wildcard_bad.out index 2bd459ff6..650f4b90c 100644 --- a/test_regress/t/t_assoc_wildcard_bad.out +++ b/test_regress/t/t_assoc_wildcard_bad.out @@ -70,4 +70,8 @@ : ... In instance t 43 | a[x] = "bad"; | ^ +%Error: t/t_assoc_wildcard_bad.v:45:9: Unknown wildcard associative array method 'bad_not_defined' + : ... In instance t + 45 | a.bad_not_defined(); + | ^~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assoc_wildcard_bad.v b/test_regress/t/t_assoc_wildcard_bad.v index 85ebf83ba..a2325d78c 100644 --- a/test_regress/t/t_assoc_wildcard_bad.v +++ b/test_regress/t/t_assoc_wildcard_bad.v @@ -41,5 +41,7 @@ module t (/*AUTOARG*/); a.find_last_index; // Not legal on wildcard a[x] = "bad"; + + a.bad_not_defined(); end endmodule diff --git a/test_regress/t/t_assoc_wildcard_method.v b/test_regress/t/t_assoc_wildcard_method.v index c61086e7b..cb90ad605 100644 --- a/test_regress/t/t_assoc_wildcard_method.v +++ b/test_regress/t/t_assoc_wildcard_method.v @@ -11,7 +11,7 @@ module t (/*AUTOARG*/); initial begin int q[*]; - int qe[*]; // Empty + int qe [ * ]; // Empty - Note spaces around [*] for parsing coverage int qv[$]; // Value returns int qi[$]; // Index returns int i; diff --git a/test_regress/t/t_case_huge.pl b/test_regress/t/t_case_huge.pl index 73bc861e6..c8266ccbc 100755 --- a/test_regress/t/t_case_huge.pl +++ b/test_regress/t/t_case_huge.pl @@ -14,9 +14,12 @@ compile( verilator_flags2 => ["--stats"], ); -if ($Self->{vlt_all}) { +if ($Self->{vlt}) { file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 10); file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 8); +} elsif ($Self->{vltmt}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 10); + file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 9); } execute( diff --git a/test_regress/t/t_case_inside_bad.out b/test_regress/t/t_case_inside_bad.out new file mode 100644 index 000000000..b683c04e3 --- /dev/null +++ b/test_regress/t/t_case_inside_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_case_inside_bad.v:9:7: Illegal to have inside on a casex/casez + 9 | casex (1'bx) inside + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_delay_func_bad.pl b/test_regress/t/t_case_inside_bad.pl similarity index 88% rename from test_regress/t/t_delay_func_bad.pl rename to test_regress/t/t_case_inside_bad.pl index 27159da5b..a60503a1f 100755 --- a/test_regress/t/t_delay_func_bad.pl +++ b/test_regress/t/t_case_inside_bad.pl @@ -2,7 +2,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2019 by Wilson Snyder. This program is free software; you +# Copyright 2003 by Wilson Snyder. This program is free software; you # can redistribute it and/or modify it under the terms of either the GNU # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. diff --git a/test_regress/t/t_case_inside_bad.v b/test_regress/t/t_case_inside_bad.v new file mode 100644 index 000000000..aa4e967c1 --- /dev/null +++ b/test_regress/t/t_case_inside_bad.v @@ -0,0 +1,13 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + casex (1'bx) inside + default: $stop; + endcase + end +endmodule diff --git a/test_regress/t/t_cdc_async_bad.out b/test_regress/t/t_cdc_async_bad.out index 697a80fe0..3d767fbe4 100644 --- a/test_regress/t/t_cdc_async_bad.out +++ b/test_regress/t/t_cdc_async_bad.out @@ -1,14 +1,14 @@ %Warning-DEPRECATED: Option --cdc is deprecated and is planned for removal ... For warning description see https://verilator.org/warn/DEPRECATED?v=latest ... Use "/* verilator lint_off DEPRECATED */" and lint_on around source to disable this message. -%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:28:21: Logic in path that feeds async reset, via signal: 't.rst2_bad_n' - 28 | wire rst2_bad_n = rst0_n | rst1_n; - | ^ -%Warning-CDCRSTLOGIC: See details in obj_vlt/t_cdc_async_bad/Vt_cdc_async_bad__cdc.txt %Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:53:21: Logic in path that feeds async reset, via signal: 't.rst6a_bad_n' 53 | wire rst6a_bad_n = rst6_bad_n ^ $c1("0"); | ^ +%Warning-CDCRSTLOGIC: See details in obj_vlt/t_cdc_async_bad/Vt_cdc_async_bad__cdc.txt %Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:54:21: Logic in path that feeds async reset, via signal: 't.rst6b_bad_n' 54 | wire rst6b_bad_n = rst6_bad_n ^ $c1("1"); | ^ +%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:28:21: Logic in path that feeds async reset, via signal: 't.rst2_bad_n' + 28 | wire rst2_bad_n = rst0_n | rst1_n; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_cellarray.pl b/test_regress/t/t_cellarray.pl index 710539001..fa5c2725e 100755 --- a/test_regress/t/t_cellarray.pl +++ b/test_regress/t/t_cellarray.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - v_flags2 => ["--stats"], + v_flags2 => ["--stats -fno-dfg"], ); execute( diff --git a/test_regress/t/t_class_assign_bad.out b/test_regress/t/t_class_assign_bad.out new file mode 100644 index 000000000..f93dffe76 --- /dev/null +++ b/test_regress/t/t_class_assign_bad.out @@ -0,0 +1,17 @@ +%Error: t/t_class_assign_bad.v:16:9: Assign RHS expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 16 | c = 0; + | ^ +%Error: t/t_class_assign_bad.v:17:9: Assign RHS expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 17 | c = 1; + | ^ +%Error: t/t_class_assign_bad.v:18:7: Function Argument expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 18 | t(0); + | ^ +%Error: t/t_class_assign_bad.v:19:7: Function Argument expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 19 | t(1); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_delay.pl b/test_regress/t/t_class_assign_bad.pl similarity index 90% rename from test_regress/t/t_timing_intra_assign_delay.pl rename to test_regress/t/t_class_assign_bad.pl index d61820774..b59a5c675 100755 --- a/test_regress/t/t_timing_intra_assign_delay.pl +++ b/test_regress/t/t_class_assign_bad.pl @@ -10,8 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], +compile( fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_class_assign_bad.v b/test_regress/t/t_class_assign_bad.v new file mode 100644 index 000000000..97b7b9f46 --- /dev/null +++ b/test_regress/t/t_class_assign_bad.v @@ -0,0 +1,21 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class Cls; +endclass : Cls + +module t (/*AUTOARG*/); + Cls c; + + task t(Cls c); endtask + + initial begin + c = 0; + c = 1; + t(0); + t(1); + end +endmodule diff --git a/test_regress/t/t_class_member_sens.pl b/test_regress/t/t_class_member_sens.pl new file mode 100755 index 000000000..cf6d969a8 --- /dev/null +++ b/test_regress/t/t_class_member_sens.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + sanitize => 1, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_member_sens.v b/test_regress/t/t_class_member_sens.v new file mode 100644 index 000000000..f178f995b --- /dev/null +++ b/test_regress/t/t_class_member_sens.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + class EventClass; + event e; + endclass + + EventClass ec = new; + int cyc = 0; + + always @ec.e ec = new; + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) ->ec.e; + else if (cyc == 2) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_class_param_bad.out b/test_regress/t/t_class_param_bad.out deleted file mode 100644 index ef96bc2f3..000000000 --- a/test_regress/t/t_class_param_bad.out +++ /dev/null @@ -1,9 +0,0 @@ -%Error-PINNOTFOUND: t/t_class_param_bad.v:12:11: Parameter pin not found: 'PARAMBAD' - : ... Suggested alternative: 'PARAMB' - 12 | Cls #(.PARAMBAD(1)) c; - | ^~~~~~~~ - ... For error description see https://verilator.org/warn/PINNOTFOUND?v=latest -%Error-PINNOTFOUND: t/t_class_param_bad.v:13:14: Parameter pin not found: '__paramNumber2' - 13 | Cls #(13, 1) cd; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_class_param_bad1.out b/test_regress/t/t_class_param_bad1.out new file mode 100644 index 000000000..756f6e838 --- /dev/null +++ b/test_regress/t/t_class_param_bad1.out @@ -0,0 +1,9 @@ +%Error-PINNOTFOUND: t/t_class_param_bad1.v:12:11: Parameter pin not found: 'PARAMBAD' + : ... Suggested alternative: 'PARAMB' + 12 | Cls #(.PARAMBAD(1)) c; + | ^~~~~~~~ + ... For error description see https://verilator.org/warn/PINNOTFOUND?v=latest +%Error-PINNOTFOUND: t/t_class_param_bad1.v:13:14: Parameter pin not found: '__paramNumber2' + 13 | Cls #(13, 1) cd; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_class_param_bad.pl b/test_regress/t/t_class_param_bad1.pl similarity index 100% rename from test_regress/t/t_class_param_bad.pl rename to test_regress/t/t_class_param_bad1.pl diff --git a/test_regress/t/t_class_param_bad.v b/test_regress/t/t_class_param_bad1.v similarity index 100% rename from test_regress/t/t_class_param_bad.v rename to test_regress/t/t_class_param_bad1.v diff --git a/test_regress/t/t_class_param_bad2.out b/test_regress/t/t_class_param_bad2.out new file mode 100644 index 000000000..35b901184 --- /dev/null +++ b/test_regress/t/t_class_param_bad2.out @@ -0,0 +1,5 @@ +%Error: t/t_class_param_bad2.v:12:4: Missing type parameter: 'PARAMB' + : ... In instance t + 12 | Cls c; + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_class_param_bad2.pl b/test_regress/t/t_class_param_bad2.pl new file mode 100755 index 000000000..19ba90d40 --- /dev/null +++ b/test_regress/t/t_class_param_bad2.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_param_bad2.v b/test_regress/t/t_class_param_bad2.v new file mode 100644 index 000000000..9332fdde8 --- /dev/null +++ b/test_regress/t/t_class_param_bad2.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class Cls #(type PARAMB); +endclass + +module t (/*AUTOARG*/); + + Cls c; // Missing type param + +endmodule diff --git a/test_regress/t/t_class_param_type.pl b/test_regress/t/t_class_param_type.pl new file mode 100755 index 000000000..aabcde63e --- /dev/null +++ b/test_regress/t/t_class_param_type.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_param_type.v b/test_regress/t/t_class_param_type.v new file mode 100644 index 000000000..1501f0819 --- /dev/null +++ b/test_regress/t/t_class_param_type.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_class_param.v + +class Parcls #(type T); + static function int get_p; + return T::get_p(); + endfunction +endclass + +class Cls; + static function int get_p; + return 20; + endfunction +endclass + +typedef Cls cls_t; +typedef cls_t cls2_t; + +module t (/*AUTOARG*/); + + initial begin + if (Parcls#(cls2_t)::get_p() != 20) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_unsup_bad.out b/test_regress/t/t_class_unsup_bad.out index eba10d537..4e3bc999f 100644 --- a/test_regress/t/t_class_unsup_bad.out +++ b/test_regress/t/t_class_unsup_bad.out @@ -1,10 +1,3 @@ -%Error-UNSUPPORTED: t/t_class_unsup_bad.v:7:1: Unsupported: virtual interface - 7 | virtual interface vi_t vi; - | ^~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_class_unsup_bad.v:8:1: Unsupported: virtual data type - 8 | virtual vi_t vi2; - | ^~~~~~~ %Error: t/t_class_unsup_bad.v:29:24: Syntax error: 'const'/'rand'/'randc' not allowed before function/task declaration 29 | const function void func_const; endfunction | ^~~~~~~~~~ diff --git a/test_regress/t/t_class_uses_this.v b/test_regress/t/t_class_uses_this.v index 531c6e1cb..966f8bb23 100644 --- a/test_regress/t/t_class_uses_this.v +++ b/test_regress/t/t_class_uses_this.v @@ -11,13 +11,25 @@ class Cls; this.addr = addr; end : body endfunction + function void set2(bit [3:0] addr); + begin : body + Cls c2 = this; + c2.addr = addr; + end : body + endfunction extern function void setext(bit [3:0] addr); + extern function void setext2(bit [3:0] addr); endclass function void Cls::setext(bit [3:0] addr); this.addr = addr; endfunction +function void Cls::setext2(bit [3:0] addr); + Cls c2 = this; + c2.addr = addr; +endfunction + module t(/*AUTOARG*/ // Inputs clk @@ -34,8 +46,12 @@ module t(/*AUTOARG*/ $display(baz.addr); `endif if (bar.addr != 4) $stop; + bar.set2(1); + if (bar.addr != 1) $stop; bar.setext(2); if (bar.addr != 2) $stop; + bar.setext2(3); + if (bar.addr != 3) $stop; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_class_vparam.v b/test_regress/t/t_class_vparam.v index 660dbb5fa..6d927d6ac 100644 --- a/test_regress/t/t_class_vparam.v +++ b/test_regress/t/t_class_vparam.v @@ -14,7 +14,7 @@ virtual class vclass #(type CTYPE_t = arg_class_t, int I = 0); pure virtual function void funcname(paramed_class_t #(CTYPE_t) v); endclass -class paramed_class_t #(type TYPE = int, int I = 0); +class paramed_class_t #(type TYPE, int I = 0); TYPE memb; endclass diff --git a/test_regress/t/t_clk_condflop_nord.v b/test_regress/t/t_clk_condflop_nord.v deleted file mode 100644 index a28335121..000000000 --- a/test_regress/t/t_clk_condflop_nord.v +++ /dev/null @@ -1,127 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2005 by Wilson Snyder. -// SPDX-License-Identifier: CC0-1.0 - -module t (clk); - input clk; - - reg [0:0] d1; - reg [2:0] d3; - reg [7:0] d8; - - wire [0:0] q1; - wire [2:0] q3; - wire [7:0] q8; - - // verilator lint_off UNOPTFLAT - reg ena; - // verilator lint_on UNOPTFLAT - - condff #(12) condff - (.clk(clk), .sen(1'b0), .ena(ena), - .d({d8,d3,d1}), - .q({q8,q3,q1})); - - integer cyc; initial cyc=1; - always @ (posedge clk) begin - if (cyc!=0) begin - //$write("%x %x %x %x\n", cyc, q8, q3, q1); - cyc <= cyc + 1; - if (cyc==1) begin - d1 <= 1'b1; d3<=3'h1; d8<=8'h11; - ena <= 1'b1; - end - if (cyc==2) begin - d1 <= 1'b0; d3<=3'h2; d8<=8'h33; - ena <= 1'b0; - end - if (cyc==3) begin - d1 <= 1'b1; d3<=3'h3; d8<=8'h44; - ena <= 1'b1; - if (q8 != 8'h11) $stop; - end - if (cyc==4) begin - d1 <= 1'b1; d3<=3'h4; d8<=8'h77; - ena <= 1'b1; - if (q8 != 8'h11) $stop; - end - if (cyc==5) begin - d1 <= 1'b1; d3<=3'h0; d8<=8'h88; - ena <= 1'b1; - if (q8 != 8'h44) $stop; - end - if (cyc==6) begin - if (q8 != 8'h77) $stop; - end - if (cyc==7) begin - if (q8 != 8'h88) $stop; - end - // - if (cyc==20) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end - end -endmodule - -module condff (clk, sen, ena, d, q); - parameter WIDTH = 1; - input clk; - - input sen; - input ena; - input [WIDTH-1:0] d; - output [WIDTH-1:0] q; - - condffimp #(.WIDTH(WIDTH)) - imp (.clk(clk), .sen(sen), .ena(ena), .d(d), .q(q)); -endmodule - -module condffimp (clk, sen, ena, d, q); - parameter WIDTH = 1; - input clk; - input sen; - input ena; - input [WIDTH-1:0] d; - output reg [WIDTH-1:0] q; - wire gatedclk; - - clockgate clockgate (.clk(clk), .sen(sen), .ena(ena), .gatedclk(gatedclk)); - - always @(posedge gatedclk) begin - if (gatedclk === 1'bX) begin - q <= {WIDTH{1'bX}}; - end - else begin - q <= d; - end - end - -endmodule - -module clockgate (clk, sen, ena, gatedclk); - input clk; - input sen; - input ena; - output gatedclk; - - reg ena_b; - wire gatedclk = clk & ena_b; - - // verilator lint_off COMBDLY - // verilator lint_off LATCH - always @(clk or ena or sen) begin - if (~clk) begin - ena_b <= ena | sen; - end - else begin - if ((clk^sen)===1'bX) ena_b <= 1'bX; - end - end - // verilator lint_on LATCH - // verilator lint_on COMBDLY - -endmodule diff --git a/test_regress/t/t_clk_dpulse.v b/test_regress/t/t_clk_dpulse.v index fdb1b2499..5ab490ffa 100644 --- a/test_regress/t/t_clk_dpulse.v +++ b/test_regress/t/t_clk_dpulse.v @@ -11,8 +11,6 @@ module t (/*AUTOARG*/ input clk; - // verilator lint_off GENCLK - reg [7:0] cyc; initial cyc = 0; reg genclk; // verilator lint_off MULTIDRIVEN diff --git a/test_regress/t/t_clk_dsp.v b/test_regress/t/t_clk_dsp.v index 0f656e3df..6e761b17b 100644 --- a/test_regress/t/t_clk_dsp.v +++ b/test_regress/t/t_clk_dsp.v @@ -11,8 +11,6 @@ module t (/*AUTOARG*/ input clk; - // verilator lint_off GENCLK - reg [7:0] cyc; initial cyc = 0; reg [7:0] padd; reg dsp_ph1, dsp_ph2, dsp_reset; diff --git a/test_regress/t/t_clk_first.v b/test_regress/t/t_clk_first.v index 03ae64841..b2de33e7c 100644 --- a/test_regress/t/t_clk_first.v +++ b/test_regress/t/t_clk_first.v @@ -52,10 +52,8 @@ module t_clk (/*AUTOARG*/ // verilator lint_on MULTIDRIVEN reg [7:0] int_clocks_copy; - // verilator lint_off GENCLK reg internal_clk; initial internal_clk = 0; reg reset_int_; - // verilator lint_on GENCLK always @ (posedge clk) begin `ifdef TEST_VERBOSE @@ -170,9 +168,7 @@ module t_clk_two (/*AUTOARG*/ ); input fastclk; input reset_l; - // verilator lint_off GENCLK reg clk2; - // verilator lint_on GENCLK reg [31:0] count; t_clk_twob tb (.*); diff --git a/test_regress/t/t_clk_gen.v b/test_regress/t/t_clk_gen.v index fcc149f68..34b4d2f7b 100644 --- a/test_regress/t/t_clk_gen.v +++ b/test_regress/t/t_clk_gen.v @@ -12,7 +12,6 @@ module t (/*AUTOARG*/ input clk; integer cyc; initial cyc=1; - // verilator lint_off GENCLK reg gendlyclk_r; reg [31:0] gendlydata_r; reg [31:0] dlydata_gr; diff --git a/test_regress/t/t_clk_latch.pl b/test_regress/t/t_clk_latch.pl index 1f8ac0132..ce1f8586f 100755 --- a/test_regress/t/t_clk_latch.pl +++ b/test_regress/t/t_clk_latch.pl @@ -10,14 +10,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -my $fail = $Self->{vlt_all}; - compile( ); execute( - check_finished => !$fail, - fails => $fail, + check_finished => 1 ); ok(1); diff --git a/test_regress/t/t_clk_latch_edgestyle.pl b/test_regress/t/t_clk_latch_edgestyle.pl index ca72a6975..52730f4f4 100755 --- a/test_regress/t/t_clk_latch_edgestyle.pl +++ b/test_regress/t/t_clk_latch_edgestyle.pl @@ -12,16 +12,14 @@ scenarios(simulator => 1); top_filename("t/t_clk_latch.v"); -my $fail = $Self->{vlt_all}; compile( v_flags2 => ['+define+EDGE_DETECT_STYLE'], - fails => $fail, ); execute( - check_finished => !$fail, - ) if !$fail; + check_finished => 1 + ); ok(1); 1; diff --git a/test_regress/t/t_clk_latchgate.v b/test_regress/t/t_clk_latchgate.v index c6ad11ca8..db9dae13d 100644 --- a/test_regress/t/t_clk_latchgate.v +++ b/test_regress/t/t_clk_latchgate.v @@ -19,7 +19,6 @@ `define GATED_CLK_TESTCASE 1 // A side effect of the problem is this warning, disabled by default -//verilator lint_on IMPERFECTSCH // Test Bench module t (/*AUTOARG*/ @@ -153,7 +152,7 @@ module Test (/*AUTOARG*/ output wire [7:0] entry_vld; wire [7:0] gclk_vld; - wire [7:0] ff_en_vld /*verilator clock_enable*/; + wire [7:0] ff_en_vld; reg [7:0] flop_en_vld; always @(posedge clk) flop_en_vld <= ff_en_e1; diff --git a/test_regress/t/t_clk_powerdn.v b/test_regress/t/t_clk_powerdn.v index 067ead2a4..e8dec908f 100644 --- a/test_regress/t/t_clk_powerdn.v +++ b/test_regress/t/t_clk_powerdn.v @@ -13,8 +13,6 @@ module t (/*AUTOARG*/ reg reset_l; - // verilator lint_off GENCLK - /*AUTOWIRE*/ // Beginning of automatic wires (for undeclared instantiated-module outputs) // End of automatics diff --git a/test_regress/t/t_clk_scope_bad.out b/test_regress/t/t_clk_scope_bad.out deleted file mode 100644 index 024049366..000000000 --- a/test_regress/t/t_clk_scope_bad.out +++ /dev/null @@ -1,7 +0,0 @@ -%Warning-CLKDATA: t/t_clk_scope_bad.v:36:12: Clock used as data (on rhs of assignment) in sequential block 'clk' - : ... In instance t.p2 - 36 | q <= d; - | ^ - ... For warning description see https://verilator.org/warn/CLKDATA?v=latest - ... Use "/* verilator lint_off CLKDATA */" and lint_on around source to disable this message. -%Error: Exiting due to diff --git a/test_regress/t/t_clk_scope_bad.pl b/test_regress/t/t_clk_scope_bad.pl index 35d749208..cc34dc85f 100755 --- a/test_regress/t/t_clk_scope_bad.pl +++ b/test_regress/t/t_clk_scope_bad.pl @@ -10,10 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(linter => 1); -lint( - fails => 1, - expect_filename => $Self->{golden_filename}, - ); +lint(); ok(1); 1; diff --git a/test_regress/t/t_clocker.pl b/test_regress/t/t_clocker.pl index 46b824b58..1af79252d 100755 --- a/test_regress/t/t_clocker.pl +++ b/test_regress/t/t_clocker.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["--trace", "-Wno-CLKDATA"] + verilator_flags2 => ["--trace"] ); execute( diff --git a/test_regress/t/t_clocker.v b/test_regress/t/t_clocker.v index c71e22c68..7b84db7d8 100644 --- a/test_regress/t/t_clocker.v +++ b/test_regress/t/t_clocker.v @@ -39,9 +39,6 @@ module t (/*AUTOARG*/ assign clk_3 = {3{clk_1}}; assign clk_final = clk_3[0]; - // the following two assignment triggers the CLKDATA warning - // because on LHS there are a mix of signals both CLOCK and - // DATA assign res8 = {clk_3, 1'b0, clk_4}; assign res16 = {count, clk_3, clk_1, clk_4}; @@ -52,8 +49,6 @@ module t (/*AUTOARG*/ always @(posedge clk_final or negedge clk_final) begin count = count + 1; - // the following assignment should trigger the CLKDATA warning - // because CLOCK signal is used as DATA in sequential block res <= clk_final; if ( count == 8'hf) begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_clocker_bad.out b/test_regress/t/t_clocker_bad.out deleted file mode 100644 index c32ce2551..000000000 --- a/test_regress/t/t_clocker_bad.out +++ /dev/null @@ -1,12 +0,0 @@ -%Warning-CLKDATA: t/t_clocker.v:45:17: Clock is assigned to part of data signal 'res8' - 45 | assign res8 = {clk_3, 1'b0, clk_4}; - | ^ - ... For warning description see https://verilator.org/warn/CLKDATA?v=latest - ... Use "/* verilator lint_off CLKDATA */" and lint_on around source to disable this message. -%Warning-CLKDATA: t/t_clocker.v:46:17: Clock is assigned to part of data signal 'res16' - 46 | assign res16 = {count, clk_3, clk_1, clk_4}; - | ^ -%Warning-CLKDATA: t/t_clocker.v:57:14: Clock used as data (on rhs of assignment) in sequential block 'clk' - 57 | res <= clk_final; - | ^~~~~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_comb_input_0.cpp b/test_regress/t/t_comb_input_0.cpp new file mode 100644 index 000000000..03db9b00d --- /dev/null +++ b/test_regress/t/t_comb_input_0.cpp @@ -0,0 +1,41 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "verilated.h" + +#include "Vt_comb_input_0.h" +#include "Vt_comb_input_0__Syms.h" + +#include + +int main(int argc, char** argv, char** env) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->commandArgs(argc, argv); + contextp->debug(0); + srand48(5); + + const std::unique_ptr topp{new Vt_comb_input_0}; + topp->inc = 1; + topp->clk = false; + topp->eval(); + + while (!contextp->gotFinish() && contextp->time() < 100000) { + contextp->timeInc(5); + if (topp->clk) topp->inc += 1; + topp->clk = !topp->clk; + topp->eval(); + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + return 0; +} diff --git a/test_regress/t/t_comb_input_0.pl b/test_regress/t/t_comb_input_0.pl new file mode 100755 index 000000000..0bb6b8592 --- /dev/null +++ b/test_regress/t/t_comb_input_0.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + #make_top_shell => 0, + make_main => 0, + v_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_comb_input_0.v b/test_regress/t/t_comb_input_0.v new file mode 100644 index 000000000..d66ba66c8 --- /dev/null +++ b/test_regress/t/t_comb_input_0.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + + +module top( + clk, + inc +); + + input clk; + input [31:0] inc; + + // Cycle count + reg [31:0] cyc = 0; + + // Combinational logic driven from primary input + wire [31:0] sum = cyc + inc; + + always @(posedge clk) begin + $display("cyc: %d sum: %d", cyc, sum); + if (sum != 2*cyc + 1) $stop; + cyc <= cyc + 1; + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_comb_input_1.cpp b/test_regress/t/t_comb_input_1.cpp new file mode 100644 index 000000000..ffd4c0e52 --- /dev/null +++ b/test_regress/t/t_comb_input_1.cpp @@ -0,0 +1,41 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "verilated.h" + +#include "Vt_comb_input_1.h" +#include "Vt_comb_input_1__Syms.h" + +#include + +int main(int argc, char** argv, char** env) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->commandArgs(argc, argv); + contextp->debug(0); + srand48(5); + + const std::unique_ptr topp{new Vt_comb_input_1}; + topp->inc = 1; + topp->clk = false; + topp->eval(); + + while (!contextp->gotFinish() && contextp->time() < 100000) { + contextp->timeInc(5); + if (topp->clk) topp->inc += 1; + topp->clk = !topp->clk; + topp->eval(); + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + return 0; +} diff --git a/test_regress/t/t_comb_input_1.pl b/test_regress/t/t_comb_input_1.pl new file mode 100755 index 000000000..0bb6b8592 --- /dev/null +++ b/test_regress/t/t_comb_input_1.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + #make_top_shell => 0, + make_main => 0, + v_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_comb_input_1.v b/test_regress/t/t_comb_input_1.v new file mode 100644 index 000000000..0d057b28d --- /dev/null +++ b/test_regress/t/t_comb_input_1.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + + +module top( + clk, + inc +); + + input clk; + input [31:0] inc; + + // Cycle count + reg [31:0] cyc = 0; + + /* verilator lint_off UNOPTFLAT */ + // Circular combinational logic driven from primary input. 'msb' is the + // narrowest, so it will be the cut vertex. + wire [31:0] feedback; + wire [31:0] sum = cyc + inc + feedback; + wire msb = sum[31]; // Always 0, but Verilator cannot know that + assign feedback = {32{msb}}; + + always @(posedge clk) begin + $display("cyc: %d sum: %d", cyc, sum); + if (sum != 2*cyc + 1) $stop; + cyc <= cyc + 1; + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_comb_input_2.cpp b/test_regress/t/t_comb_input_2.cpp new file mode 100644 index 000000000..69138e068 --- /dev/null +++ b/test_regress/t/t_comb_input_2.cpp @@ -0,0 +1,41 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "verilated.h" + +#include "Vt_comb_input_2.h" +#include "Vt_comb_input_2__Syms.h" + +#include + +int main(int argc, char** argv, char** env) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->commandArgs(argc, argv); + contextp->debug(0); + srand48(5); + + const std::unique_ptr topp{new Vt_comb_input_2}; + topp->inc = 1; + topp->clk = false; + topp->eval(); + + while (!contextp->gotFinish() && contextp->time() < 100000) { + contextp->timeInc(5); + if (topp->clk) topp->inc += 1; + topp->clk = !topp->clk; + topp->eval(); + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + return 0; +} diff --git a/test_regress/t/t_comb_input_2.pl b/test_regress/t/t_comb_input_2.pl new file mode 100755 index 000000000..0bb6b8592 --- /dev/null +++ b/test_regress/t/t_comb_input_2.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + #make_top_shell => 0, + make_main => 0, + v_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_comb_input_2.v b/test_regress/t/t_comb_input_2.v new file mode 100644 index 000000000..f90d5bb9b --- /dev/null +++ b/test_regress/t/t_comb_input_2.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE (|($random | $random)) +`endif + +module top( + clk, + inc +); + + input clk; + input [31:0] inc; + + // Cycle count + reg [31:0] cyc = 0; + + /* verilator lint_off UNOPTFLAT */ + // Circular combinational logic driven from primary input, but with the + // cycle itself not involving the primary input + wire [31:0] dup = `IMPURE_ONE ? inc : 32'd0; + wire [31:0] feedback; + wire [31:0] sum = cyc + dup + feedback; + wire msb = sum[31]; // Always 0, but Verilator cannot know that + assign feedback = {32{msb}}; + + + always @(posedge clk) begin + $display("cyc: %d sum: %d", cyc, sum); + if (sum != 2*cyc + 1) $stop; + cyc <= cyc + 1; + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_comb_loop_through_unpacked_array.pl b/test_regress/t/t_comb_loop_through_unpacked_array.pl new file mode 100755 index 000000000..c7f63144c --- /dev/null +++ b/test_regress/t/t_comb_loop_through_unpacked_array.pl @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + verilator_flags2 => ["-Wno-UNOPTFLAT"] + ); + +ok(1); +1; diff --git a/test_regress/t/t_comb_loop_through_unpacked_array.v b/test_regress/t/t_comb_loop_through_unpacked_array.v new file mode 100644 index 000000000..1e2d26052 --- /dev/null +++ b/test_regress/t/t_comb_loop_through_unpacked_array.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module top( + input wire a, + input wire b, + output wire o +); + + logic [255:0] array [1:0]; + logic [255:0] tmp [1:0]; + + // Nonsensical, but needs to compile. (In some real designs we can end up + // with combinational loops via unpacked arrays) + always_comb begin + tmp[0] = array[a]; + end + + always_comb begin + array[b] = tmp[0]; + end + + assign o = array[0][0]; + +endmodule diff --git a/test_regress/t/t_const_opt.pl b/test_regress/t/t_const_opt.pl index 36f064cb4..1ee302ca7 100755 --- a/test_regress/t/t_const_opt.pl +++ b/test_regress/t/t_const_opt.pl @@ -11,7 +11,8 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats", "$Self->{t_dir}/$Self->{name}.cpp"], + verilator_flags2 => ["-Wno-UNOPTTHREADS", "-fno-dfg", + "--stats", "$Self->{t_dir}/$Self->{name}.cpp"], ); execute( diff --git a/test_regress/t/t_const_sel_sel_extend.pl b/test_regress/t/t_const_sel_sel_extend.pl new file mode 100755 index 000000000..84ae125be --- /dev/null +++ b/test_regress/t/t_const_sel_sel_extend.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile(); + +ok(1); +1; diff --git a/test_regress/t/t_const_sel_sel_extend.v b/test_regress/t/t_const_sel_sel_extend.v new file mode 100644 index 000000000..c695de4f2 --- /dev/null +++ b/test_regress/t/t_const_sel_sel_extend.v @@ -0,0 +1,22 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t( + output wire res +); + + function automatic logic foo(logic bar); + foo = '0; + endfunction + + logic a, b; + logic [0:0][1:0] array; + + assign b = 0; + assign a = foo(b); + assign res = array[a][a]; + +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 75c4736a9..749f294de 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -163,7 +163,7 @@ module Vt_debug_emitv_t; $display("stmt"); end end - always @([any] in) begin + always @([changed] in) begin begin $display("stmt"); end diff --git a/test_regress/t/t_dedupe_clk_gate.pl b/test_regress/t/t_dedupe_clk_gate.pl index ccc0816ac..01b1afad8 100755 --- a/test_regress/t/t_dedupe_clk_gate.pl +++ b/test_regress/t/t_dedupe_clk_gate.pl @@ -13,11 +13,11 @@ scenarios(simulator => 1); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - verilator_flags2 => ["--stats $Self->{t_dir}/t_dedupe_clk_gate.vlt"], + verilator_flags2 => ["--stats"], ); if ($Self->{vlt_all}) { - file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); file_grep($Self->{stats}, qr/Optimizations, Gate sigs deduped\s+(\d+)/i, 4); } diff --git a/test_regress/t/t_dedupe_clk_gate.v b/test_regress/t/t_dedupe_clk_gate.v index af6331196..6ba6ff174 100644 --- a/test_regress/t/t_dedupe_clk_gate.v +++ b/test_regress/t/t_dedupe_clk_gate.v @@ -53,7 +53,7 @@ endmodule module clock_gate_flop (gated_clk, clk, clken); output gated_clk; input clk, clken; - reg clken_r /*verilator clock_enable*/; + reg clken_r; assign gated_clk = clk & clken_r ; always @(negedge clk) diff --git a/test_regress/t/t_dedupe_clk_gate.vlt b/test_regress/t/t_dedupe_clk_gate.vlt deleted file mode 100644 index 6e2a77ef9..000000000 --- a/test_regress/t/t_dedupe_clk_gate.vlt +++ /dev/null @@ -1,3 +0,0 @@ -`verilator_config - -clock_enable -module "clock_gate_latch" -var "clken_latched" diff --git a/test_regress/t/t_delay.pl b/test_regress/t/t_delay.pl index cf79784fb..3e8a3c919 100755 --- a/test_regress/t/t_delay.pl +++ b/test_regress/t/t_delay.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ['-Wno-STMTDLY -Wno-ASSIGNDLY'], + verilator_flags2 => ['-Wno-STMTDLY -Wno-ASSIGNDLY --no-timing'], ); execute( diff --git a/test_regress/t/t_delay.v b/test_regress/t/t_delay.v index c391c5b61..628bc3c32 100644 --- a/test_regress/t/t_delay.v +++ b/test_regress/t/t_delay.v @@ -4,6 +4,8 @@ // any use, without warranty, 2003 by Wilson Snyder. // SPDX-License-Identifier: CC0-1.0 +`timescale 100ns/1ns + module t (/*AUTOARG*/ // Inputs clk @@ -15,11 +17,15 @@ module t (/*AUTOARG*/ reg [31:0] dly0; wire [31:0] dly1; wire [31:0] dly2 = dly1 + 32'h1; + wire [31:0] dly3; typedef struct packed { int dly; } dly_s_t; dly_s_t dly_s; assign #(1.2000000000000000) dly1 = dly0 + 32'h1; + assign #(sub.delay) dly3 = dly1 + 1; + + sub sub(); always @ (posedge clk) begin cyc <= cyc + 1; @@ -39,9 +45,14 @@ module t (/*AUTOARG*/ //dly0 <= # dly_s.dly 32'h55; // Unsupported, issue-2410 end else if (cyc == 99) begin + if (dly3 !== 32'h57) $stop; $write("*-* All Finished *-*\n"); #100 $finish; end end endmodule + +module sub; + realtime delay = 2.3; +endmodule diff --git a/test_regress/t/t_delay_func_bad.out b/test_regress/t/t_delay_func_bad.out deleted file mode 100644 index dd63449c5..000000000 --- a/test_regress/t/t_delay_func_bad.out +++ /dev/null @@ -1,9 +0,0 @@ -%Error: t/t_delay_func_bad.v:10:8: Delays are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) - : ... In instance t - 10 | #1 $stop; - | ^ -%Error: t/t_delay_func_bad.v:23:8: Delays are not legal in final blocks (IEEE 1800-2017 9.2.3) - : ... In instance t - 23 | #1; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_delay_stmtdly_bad.out b/test_regress/t/t_delay_stmtdly_bad.out index fe957acdb..ed8235f57 100644 --- a/test_regress/t/t_delay_stmtdly_bad.out +++ b/test_regress/t/t_delay_stmtdly_bad.out @@ -1,31 +1,39 @@ -%Warning-ASSIGNDLY: t/t_delay.v:22:13: Unsupported: Ignoring timing control on this assignment. +%Warning-ASSIGNDLY: t/t_delay.v:25:11: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 22 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1; - | ^~~~~~~~~~~~~~~~~~ + 25 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1; + | ^ ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_delay.v:27:19: Unsupported: Ignoring timing control on this assignment. +%Warning-ASSIGNDLY: t/t_delay.v:26:11: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 27 | dly0 <= #0 32'h11; - | ^ -%Warning-ASSIGNDLY: t/t_delay.v:30:19: Unsupported: Ignoring timing control on this assignment. + 26 | assign #(sub.delay) dly3 = dly1 + 1; + | ^ +%Warning-ASSIGNDLY: t/t_delay.v:33:18: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 30 | dly0 <= #0.12 dly0 + 32'h12; - | ^~~~ -%Warning-ASSIGNDLY: t/t_delay.v:38:26: Unsupported: Ignoring timing control on this assignment. + 33 | dly0 <= #0 32'h11; + | ^ +%Warning-ASSIGNDLY: t/t_delay.v:36:18: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 38 | dly0 <= #(dly_s.dly) 32'h55; - | ^~~ -%Warning-STMTDLY: t/t_delay.v:43:11: Unsupported: Ignoring delay on this delayed statement. + 36 | dly0 <= #0.12 dly0 + 32'h12; + | ^ +%Warning-ASSIGNDLY: t/t_delay.v:44:18: Ignoring timing control on this assignment/primitive due to --no-timing + : ... In instance t + 44 | dly0 <= #(dly_s.dly) 32'h55; + | ^ +%Warning-STMTDLY: t/t_delay.v:50:10: Ignoring delay on this statement due to --no-timing : ... In instance t - 43 | #100 $finish; - | ^~~ -%Warning-UNUSED: t/t_delay.v:20:12: Signal is not used: 'dly_s' - : ... In instance t - 20 | dly_s_t dly_s; + 50 | #100 $finish; + | ^ +%Warning-UNUSEDSIGNAL: t/t_delay.v:23:12: Signal is not used: 'dly_s' + : ... In instance t + 23 | dly_s_t dly_s; | ^~~~~ -%Warning-BLKSEQ: t/t_delay.v:37:20: Blocking assignment '=' in sequential logic process +%Warning-UNUSEDSIGNAL: t/t_delay.v:57:13: Signal is not used: 'delay' + : ... In instance t.sub + 57 | realtime delay = 2.3; + | ^~~~~ +%Warning-BLKSEQ: t/t_delay.v:43:20: Blocking assignment '=' in sequential logic process : ... Suggest using delayed assignment '<=' - 37 | dly_s.dly = 55; + 43 | dly_s.dly = 55; | ^ %Error: Exiting due to diff --git a/test_regress/t/t_delay_stmtdly_bad.pl b/test_regress/t/t_delay_stmtdly_bad.pl index 85ea0432b..59820b3fb 100755 --- a/test_regress/t/t_delay_stmtdly_bad.pl +++ b/test_regress/t/t_delay_stmtdly_bad.pl @@ -13,7 +13,7 @@ scenarios(vlt => 1); top_filename("t/t_delay.v"); lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], + verilator_flags2 => ['--no-timing -Wall -Wno-DECLFILENAME'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_delay_timing.pl b/test_regress/t/t_delay_timing.pl new file mode 100755 index 000000000..505ebf865 --- /dev/null +++ b/test_regress/t/t_delay_timing.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 10e-7 / 10e-9; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_delay.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ['--timing -Wno-ZERODLY'], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_detectarray_1.pl b/test_regress/t/t_detectarray_1.pl index 82fae48f0..973a951a0 100755 --- a/test_regress/t/t_detectarray_1.pl +++ b/test_regress/t/t_detectarray_1.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA", "-Wno-UNOPTFLAT"] + verilator_flags2 => ["-Wno-UNOPTFLAT"] ); execute( diff --git a/test_regress/t/t_detectarray_1.v b/test_regress/t/t_detectarray_1.v index aa2f20e04..df266bb06 100644 --- a/test_regress/t/t_detectarray_1.v +++ b/test_regress/t/t_detectarray_1.v @@ -1,7 +1,5 @@ // DESCRIPTION: Verilator: Simple test of unoptflat // -// Trigger the DETECTARRAY error. -// // This file ONLY is placed into the Public Domain, for any use, // without warranty, 2013 by Jeremy Bennett. // SPDX-License-Identifier: CC0-1.0 diff --git a/test_regress/t/t_detectarray_2.pl b/test_regress/t/t_detectarray_2.pl index 82fae48f0..973a951a0 100755 --- a/test_regress/t/t_detectarray_2.pl +++ b/test_regress/t/t_detectarray_2.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA", "-Wno-UNOPTFLAT"] + verilator_flags2 => ["-Wno-UNOPTFLAT"] ); execute( diff --git a/test_regress/t/t_detectarray_2.v b/test_regress/t/t_detectarray_2.v index 72df7ad80..141fc500e 100644 --- a/test_regress/t/t_detectarray_2.v +++ b/test_regress/t/t_detectarray_2.v @@ -1,9 +1,5 @@ // DESCRIPTION: Verilator: Simple test of unoptflat // -// This should trigger the DETECTARRAY error like t_detectarray_1.v, but in -// fact it casuses a broken link error. The only difference is that the struct -// is defined using a constant rather than a localparam. -// // This file ONLY is placed into the Public Domain, for any use, // without warranty, 2013 by Jeremy Bennett. // SPDX-License-Identifier: CC0-1.0 diff --git a/test_regress/t/t_detectarray_3.v b/test_regress/t/t_detectarray_3.v index 13b1e0592..07e7c1be2 100644 --- a/test_regress/t/t_detectarray_3.v +++ b/test_regress/t/t_detectarray_3.v @@ -1,7 +1,5 @@ // DESCRIPTION: Verilator: Simple test of unoptflat // -// Trigger the DETECTARRAY error on packed structure. -// // This file ONLY is placed into the Public Domain, for any use, // without warranty, 2014 by Jie Xu. // SPDX-License-Identifier: CC0-1.0 diff --git a/test_regress/t/t_dfg_3676.pl b/test_regress/t/t_dfg_3676.pl new file mode 100755 index 000000000..84ae125be --- /dev/null +++ b/test_regress/t/t_dfg_3676.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile(); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_3676.v b/test_regress/t/t_dfg_3676.v new file mode 100644 index 000000000..8f01e471a --- /dev/null +++ b/test_regress/t/t_dfg_3676.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +// verilator lint_off UNOPTFLAT + +module t( + input wire [3:0] i, + output wire [2:0][3:0] o +); + + wire [2:0][3:0] v; + + // This circular logic used to trip up DFG decomposition + + assign v[0] = i; + assign v[1][0] = v[0][1] | v[0][0]; + + assign o[1][2] = v[0][2]; + assign o[2][1:0] = {v[1][0] , o[1][0]}; + +endmodule diff --git a/test_regress/t/t_dfg_3679.pl b/test_regress/t/t_dfg_3679.pl new file mode 100755 index 000000000..1aa73f80a --- /dev/null +++ b/test_regress/t/t_dfg_3679.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_3679.v b/test_regress/t/t_dfg_3679.v new file mode 100644 index 000000000..206dd4691 --- /dev/null +++ b/test_regress/t/t_dfg_3679.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + + input clk; + integer cyc=1; + + reg [31:0] dly0; + + // DFG can fold this into 'dly3 = dly1 = dly0 + 1' and 'dly2 = dly0 + 2', + // but the 'dly0 + 1' term having multiple sinks needs to considered. + wire [31:0] dly1 = dly0 + 32'h1; + wire [31:0] dly2 = dly1 + 32'h1; + wire [31:0] dly3 = dly0 + 32'h1; + + always @ (posedge clk) begin + $display("[%0t] dly0=%h dly1=%h dly2=%h dly3=%h", $time, dly0, dly1, dly2, dly3); + cyc <= cyc + 1; + if (cyc == 1) begin + dly0 <= 32'h55; + end + else if (cyc == 3) begin + if (dly1 !== 32'h56) $stop; + if (dly2 !== 32'h57) $stop; + if (dly3 !== 32'h56) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_dfg_circular.pl b/test_regress/t/t_dfg_circular.pl new file mode 100755 index 000000000..a4e59f8b5 --- /dev/null +++ b/test_regress/t/t_dfg_circular.pl @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + verilator_flags2 => ["--dumpi-dfg 9"] + ); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_circular.v b/test_regress/t/t_dfg_circular.v new file mode 100644 index 000000000..3d8e51f3a --- /dev/null +++ b/test_regress/t/t_dfg_circular.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +// verilator lint_off UNOPTFLAT + +module t ( + input wire i, + output wire o +); + wire a; + wire b; + wire c; + wire d; + + assign c = i + 1'b1; + assign d = c + 1'b1; + assign a = b + d; + assign b = a + 1'b1; + + wire p; + wire q; + wire r; + wire s; + + assign p = i + 1'b1; + assign q = p + 1'b1; + assign r = s ^ q; + assign s = r + 1'b1; + + wire x; + wire y; + wire z; + wire w; + + assign x = y ^ i; + assign y = x; + assign z = w; + assign w = y & z; + + assign o = b | x; +endmodule diff --git a/test_regress/t/t_dfg_peephole.cpp b/test_regress/t/t_dfg_peephole.cpp new file mode 100644 index 000000000..6a6bf3440 --- /dev/null +++ b/test_regress/t/t_dfg_peephole.cpp @@ -0,0 +1,57 @@ +// +// DESCRIPTION: Verilator: DFG optimzier equivalence testing +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 +// + +#include +#include + +#include +#include +#include + +void rngUpdate(uint64_t& x) { + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; +} + +int main(int, char**) { + // Create contexts + VerilatedContext ctx; + + // Create models + Vref ref{&ctx}; + Vopt opt{&ctx}; + + uint64_t rand_a = 0x5aef0c8dd70a4497; + uint64_t rand_b = 0xf0c0a8dd75ae4497; + uint64_t srand_a = 0x00fa8dcc7ae4957; + + for (size_t n = 0; n < 200000; ++n) { + // Update rngs + rngUpdate(rand_a); + rngUpdate(rand_b); + rngUpdate(srand_a); + + // Assign inputs + ref.rand_a = opt.rand_a = rand_a; + ref.rand_b = opt.rand_b = rand_b; + ref.srand_a = opt.srand_a = srand_a; + + // Evaluate both models + ref.eval(); + opt.eval(); + + // Check equivalence +#include "checks.h" + + // increment time + ctx.timeInc(1); + } + + std::cout << "*-* All Finished *-*\n"; +} diff --git a/test_regress/t/t_dfg_peephole.pl b/test_regress/t/t_dfg_peephole.pl new file mode 100755 index 000000000..952898fbb --- /dev/null +++ b/test_regress/t/t_dfg_peephole.pl @@ -0,0 +1,98 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +$Self->{sim_time} = 2000000; + +# Read optimizations +my @optimizations = (); +{ + my $hdrFile = "../src/V3DfgPeephole.h"; + my $hdrFh = IO::File->new("<$hdrFile") or error("$! $hdrFile"); + my $prevOpt = ""; + my $lineno = 0; + while (defined(my $line = $hdrFh->getline)) { + $lineno = $lineno + 1; + next if $line !~ /^\s*_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY\(macro, (\w+)\)/; + my $opt = $1; + error("$hdrFile:$linenno: '$opt; is not in sorted order") if $prevOpt gt $opt; + $prevOpt = $opt; + push @optimizations, $opt; + } + error("no optimizations defined in $hdrFile") if scalar @optimizations == 0; +} + +# Generate the equivalence checks and declaration boilerplate +my $rdFile = "$Self->{top_filename}"; +my $plistFile = "$Self->{obj_dir}/portlist.vh"; +my $pdeclFile = "$Self->{obj_dir}/portdecl.vh"; +my $checkFile = "$Self->{obj_dir}/checks.h"; +my $rdFh = IO::File->new("<$rdFile") or error("$! $rdFile"); +my $plistFh = IO::File->new(">$plistFile") or error("$! $plistFile"); +my $pdeclFh = IO::File->new(">$pdeclFile") or error("$! $pdeclFile"); +my $checkFh = IO::File->new(">$checkFile") or error("$! $checkFile"); +while (defined(my $line = $rdFh->getline)) { + next if $line !~ /^\s*.*`signal\((\w+),/; + my $signal = $1; + print $plistFh "$signal,\n"; + print $pdeclFh "output $signal;\n"; + print $checkFh "if (ref.$signal != opt.$signal) {\n"; + print $checkFh " std::cout << \"Mismatched $signal\" << std::endl;\n"; + print $checkFh " std::cout << \"Ref: 0x\" << std::hex << (ref.$signal + 0) << std::endl;\n"; + print $checkFh " std::cout << \"Opt: 0x\" << std::hex << (opt.$signal + 0) << std::endl;\n"; + print $checkFh " std::exit(1);\n"; + print $checkFh "}\n"; +} +close $rdFile; +close $wrFile; + + +# Compile un-optimized +compile( + verilator_flags2 => ["--stats", "--build", "-fno-dfg", "+incdir+$Self->{obj_dir}", + "-Mdir", "$Self->{obj_dir}/obj_ref", "--prefix", "Vref"], + verilator_make_gmake => 0, + verilator_make_cmake => 0 + ); + +# Compile optimized - also builds executable +compile( + verilator_flags2 => ["--stats", "--build", "--exe", "+incdir+$Self->{obj_dir}", + "-Mdir", "$Self->{obj_dir}/obj_opt", "--prefix", "Vopt", + "-fno-const-before-dfg", # Otherwise V3Const makes testing painful + "--dump-dfg", # To fill code coverage + "-CFLAGS \"-I .. -I ../obj_ref\"", + "../obj_ref/Vref__ALL.a", + "../../t/$Self->{name}.cpp"], + verilator_make_gmake => 0, + verilator_make_cmake => 0 + ); + +# Execute test to check equivalence +execute( + executable => "$Self->{obj_dir}/obj_opt/Vopt", + check_finished => 1, + ); + +sub check { + my $name = shift; + $name = lc $name; + $name =~ s/_/ /g; + file_grep("$Self->{obj_dir}/obj_opt/Vopt__stats.txt", qr/DFG\s+(pre|post) inline Peephole, ${name}\s+([1-9]\d*)/i); +} + +# Check all optimizations defined in +foreach my $opt (@optimizations) { + check($opt); +} + +ok(1); +1; diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v new file mode 100644 index 000000000..e3e3862bd --- /dev/null +++ b/test_regress/t/t_dfg_peephole.v @@ -0,0 +1,203 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +`define signal(name, expr) wire [$bits(expr)-1:0] ``name = expr + +module t ( +`include "portlist.vh" // Boilerplate generated by t_dfg_peephole.pl + rand_a, rand_b, srand_a + ); + +`include "portdecl.vh" // Boilerplate generated by t_dfg_peephole.pl + + input rand_a; + input rand_b; + input srand_a; + wire logic [63:0] rand_a; + wire logic [63:0] rand_b; + wire logic signed [63:0] srand_a; + + wire logic randbit_a = rand_a[0]; + wire logic [127:0] rand_ba = {rand_b, rand_a}; + wire logic [127:0] rand_aa = {2{rand_a}}; + wire logic [63:0] const_a; + wire logic [63:0] const_b; + wire logic signed [63:0] sconst_a; + wire logic signed [63:0] sconst_b; + wire logic [63:0] array [3:0]; + assign array[0] = (rand_a << 32) | (rand_a >> 32); + assign array[1] = (rand_a << 16) | (rand_a >> 48); + + // x, but with evaluation slightly delayed in DfgPeephole +`define DFG(x) (&16'hffff ? (x) : (~x)) + + `signal(FOLD_UNARY_CLog2, $clog2(const_a)); + `signal(FOLD_UNARY_CountOnes, $countones(const_a)); + `signal(FOLD_UNARY_IsUnknown, $isunknown(const_a)); + `signal(FOLD_UNARY_LogNot, !const_a[0]); + `signal(FOLD_UNARY_Negate, -const_a); + `signal(FOLD_UNARY_Not, ~const_a); + `signal(FOLD_UNARY_OneHot, $onehot(const_a)); + `signal(FOLD_UNARY_OneHot0, $onehot0(const_a)); + `signal(FOLD_UNARY_RedAnd, &const_a); + `signal(FOLD_UNARY_RedOr, |const_a); + `signal(FOLD_UNARY_RedXor, ^const_a); + // verilator lint_off WIDTH + wire logic [79:0] tmp_FOLD_UNARY_Extend = const_a; + wire logic signed [79:0] tmp_FOLD_UNARY_ExtendS = sconst_a; + //verilator lint_on WIDTH + `signal(FOLD_UNARY_Extend, tmp_FOLD_UNARY_Extend); + `signal(FOLD_UNARY_ExtendS, tmp_FOLD_UNARY_ExtendS); + + `signal(FOLD_BINARY_Add, const_a + const_b); + `signal(FOLD_BINARY_And, const_a & const_b); + `signal(FOLD_BINARY_Concat, {const_a, const_b}); + `signal(FOLD_BINARY_Div, const_a / 64'd3); + `signal(FOLD_BINARY_DivS, sconst_a / 64'sd3); + `signal(FOLD_BINARY_Eq, const_a == const_b); + `signal(FOLD_BINARY_Gt, const_a > const_b); + `signal(FOLD_BINARY_GtS, sconst_a > sconst_b); + `signal(FOLD_BINARY_Gte, const_a >= const_b); + `signal(FOLD_BINARY_GteS, sconst_a >= sconst_b); + `signal(FOLD_BINARY_LogAnd, const_a[0] && const_b[0]); + `signal(FOLD_BINARY_LogEq, const_a[0] <-> const_b[0]); + `signal(FOLD_BINARY_LogIf, const_a[0] -> const_b[0]); + `signal(FOLD_BINARY_LogOr, const_a[0] || const_b[0]); + `signal(FOLD_BINARY_Lt, const_a < const_b); + `signal(FOLD_BINARY_LtS, sconst_a < sconst_b); + `signal(FOLD_BINARY_Lte, const_a <= const_b); + `signal(FOLD_BINARY_LteS, sconst_a <= sconst_b); + `signal(FOLD_BINARY_ModDiv, const_a % 64'd3); + `signal(FOLD_BINARY_ModDivS, sconst_a % 64'sd3); + `signal(FOLD_BINARY_Mul, const_a * 64'd3); + `signal(FOLD_BINARY_MulS, sconst_a * 64'sd3); + `signal(FOLD_BINARY_Neq, const_a != const_b); + `signal(FOLD_BINARY_Or, const_a | const_b); + `signal(FOLD_BINARY_Pow, const_a ** 64'd2); + `signal(FOLD_BINARY_PowSS, sconst_a ** 64'sd2); + `signal(FOLD_BINARY_PowSU, sconst_a ** 64'd2); + `signal(FOLD_BINARY_PowUS, const_a ** 64'sd2); + `signal(FOLD_BINARY_Replicate, {2{const_a}}); + `signal(FOLD_BINARY_ShiftL, const_a << 2); + `signal(FOLD_BINARY_ShiftR, const_a >> 2); + `signal(FOLD_BINARY_ShiftRS, sconst_a >>> 2); + `signal(FOLD_BINARY_Sub, const_a - const_b); + `signal(FOLD_BINARY_Xor, const_a ^ const_b); + + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_And, (const_a & (const_b & rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Or, (const_a | (const_b | rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Xor, (const_a ^ (const_b ^ rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Add, (const_a + (const_b + rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Mul, (const_a * (const_b * rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_MulS, (sconst_a * (sconst_b * srand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Concat, {const_a, {const_b, rand_a}}); + + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_And, ((rand_a & const_b) & const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Or, ((rand_a | const_b) | const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Xor, ((rand_a ^ const_b) ^ const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Add, ((rand_a + const_b) + const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Mul, ((rand_a * const_b) * const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_MulS, ((srand_a * sconst_b) * sconst_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Concat, {{rand_a, const_b}, const_a}); + + `signal(FOLD_SEL, const_a[3:1]); + + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); + `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); + `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT, 32'h12345678 ^ {8'h0, rand_a[23:0]}); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT_2, 32'h12345678 ^ {rand_b[7:0], rand_a[23:0]}); + `signal(PUSH_COMPARE_OP_THROUGH_CONCAT, 4'b1011 == {2'b10, rand_a[1:0]}); + `signal(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, |(rand_a[32] ? rand_a[3:0] : 4'h0)); + `signal(REPLACE_REDUCTION_OF_CONST_AND, &const_a); + `signal(REPLACE_REDUCTION_OF_CONST_OR, |const_a); + `signal(REPLACE_REDUCTION_OF_CONST_XOR, ^const_a); + `signal(REPLACE_EXTEND, 4'(rand_a[0])); + `signal(PUSH_NOT_THROUGH_COND, ~(rand_a[0] ? rand_a[4:0] : 5'hb)); + `signal(REMOVE_NOT_NOT, ~~rand_a); + `signal(REPLACE_NOT_NEQ, ~(rand_a != rand_b)); + `signal(REPLACE_NOT_EQ, ~(rand_a == rand_b)); + `signal(REPLACE_NOT_OF_CONST, ~4'd0); + `signal(REPLACE_AND_OF_NOT_AND_NOT, ~rand_a[1] & ~rand_b[1]); + `signal(REPLACE_AND_OF_NOT_AND_NEQ, ~rand_a[2] & (rand_b != 64'd2)); + `signal(REPLACE_AND_OF_CONST_AND_CONST, const_a & const_b); + `signal(REPLACE_AND_WITH_ZERO, 64'd0 & rand_a); + `signal(REMOVE_AND_WITH_ONES, -64'd1 & rand_a); + `signal(REPLACE_CONTRADICTORY_AND, rand_a & ~rand_a); + `signal(REPLACE_OR_OF_NOT_AND_NOT, ~rand_a[3] | ~rand_b[3]); + `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~rand_a[4] | (rand_b != 64'd3)); + `signal(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, {2'd0, rand_a[1:0]} | {rand_b[1:0], 2'd0}); + `signal(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, {rand_a[1:0], 2'd0} | {2'd0, rand_b[1:0]}); + `signal(REPLACE_OR_OF_CONST_AND_CONST, const_a | const_b); + `signal(REMOVE_OR_WITH_ZERO, 64'd0 | rand_a); + `signal(REPLACE_OR_WITH_ONES, -64'd1 | rand_a); + `signal(REPLACE_TAUTOLOGICAL_OR, rand_a | ~rand_a); + `signal(REMOVE_SUB_ZERO, rand_a - 64'd0); + `signal(REPLACE_SUB_WITH_NOT, rand_a[0] - 1'b1); + `signal(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, rand_a << {2'b0, rand_a[2:0]}); + `signal(REPLACE_EQ_OF_CONST_AND_CONST, 4'd0 == 4'd1); + `signal(REMOVE_FULL_WIDTH_SEL, rand_a[63:0]); + `signal(REMOVE_SEL_FROM_RHS_OF_CONCAT, rand_ba[63:0]); + `signal(REMOVE_SEL_FROM_LHS_OF_CONCAT, rand_ba[127:64]); + `signal(PUSH_SEL_THROUGH_CONCAT, rand_ba[120:0]); + `signal(PUSH_SEL_THROUGH_REPLICATE, rand_aa[0]); + `signal(REPLACE_SEL_FROM_CONST, const_a[2]); + `signal(REPLACE_CONCAT_OF_CONSTS, {const_a, const_b}); + `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, rand_a[63:62]}); + `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {rand_a[1:0], 62'd0}); + `signal(PUSH_CONCAT_THROUGH_NOTS, {~(rand_a+64'd101), ~(rand_b+64'd101)} ); + `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {rand_a[10:3], rand_a[2:1]}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {rand_a[10:3], `DFG({rand_a[2:1], rand_b})}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, {`DFG({rand_b, rand_a[10:3]}), rand_a[2:1]}); + `signal(REMOVE_COND_WITH_FALSE_CONDITION, 1'd0 ? rand_a : rand_b); + `signal(REMOVE_COND_WITH_TRUE_CONDITION, 1'd1 ? rand_a : rand_b); + `signal(SWAP_COND_WITH_NOT_CONDITION, (~rand_a[0] & 1'd1) ? rand_a : rand_b); + `signal(SWAP_COND_WITH_NEQ_CONDITION, rand_b != rand_a ? rand_a : rand_b); + `signal(PULL_NOTS_THROUGH_COND, rand_a[0] ? ~rand_a[4:0] : ~rand_b[4:0]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ZERO, rand_a[0] ? 1'd0 : rand_a[1]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, rand_a[0] ? 1'd1 : rand_a[1]); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, rand_a[0] ? rand_a[1] : 1'd0); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, rand_a[0] ? rand_a[1] : 1'd1); + `signal(INLINE_ARRAYSEL, array[0]); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&(rand_a + 64'd105)) & (&(rand_b + 64'd108))); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|(rand_a + 64'd106)) | (|(rand_b + 64'd109))); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^(rand_a + 64'd107)) ^ (^(rand_b + 64'd110))); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &{1'd1, rand_b}); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |{1'd1, rand_b}); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^{1'd1, rand_b}); + `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &rand_a[0]); + `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |rand_a[0]); + `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^rand_a[0]); + `signal(REMOVE_XOR_WITH_ZERO, 64'd0 ^ rand_a); + `signal(REMOVE_XOR_WITH_ONES, -64'd1 ^ rand_a); + `signal(REPLACE_COND_DEC, randbit_a ? rand_b - 64'b1 : rand_b); + `signal(REPLACE_COND_INC, randbit_a ? rand_b + 64'b1 : rand_b); + `signal(NO_REPLACE_COND_DEC, randbit_a ? rand_b - 64'hf000000000000000 : rand_b); + `signal(NO_REPLACE_COND_INC, randbit_a ? rand_b + 64'hf000000000000000 : rand_b); + `signal(RIGHT_LEANING_ASSOC, (((rand_a + rand_b) + rand_a) + rand_b)); + `signal(RIGHT_LEANING_CONCET, {{{rand_a, rand_b}, rand_a}, rand_b}); + + // Some selects need extra temporaries + wire [63:0] sel_from_cond = rand_a[0] ? rand_a : const_a; + wire [63:0] sel_from_shiftl = rand_a << 10; + wire [31:0] sel_from_sel = rand_a[10+:32]; + + `signal(PUSH_SEL_THROUGH_COND, sel_from_cond[2]); + `signal(PUSH_SEL_THROUGH_SHIFTL, sel_from_shiftl[20:0]); + `signal(REPLACE_SEL_FROM_SEL, sel_from_sel[4:3]); + + // Sel from not requires the operand to have a sinle sink, so can't use + // the chekc due to the raw expression referencing the operand + wire [63:0] sel_from_not_tmp = ~(rand_a >> rand_b[2:0] << rand_a[3:0]); + wire sel_from_not = sel_from_not_tmp[2]; + always @(posedge randbit_a) if ($c(0)) $display(sel_from_not); // Do not remove signal + + // Assigned at the end to avoid inlining by other passes + assign const_a = 64'h0123456789abcdef; + assign const_b = 64'h98badefc10325647; + assign sconst_a = 64'hfedcba9876543210; + assign sconst_b = 64'hba0123456789cdef; +endmodule diff --git a/test_regress/t/t_dfg_unhandled.pl b/test_regress/t/t_dfg_unhandled.pl new file mode 100755 index 000000000..6364d038e --- /dev/null +++ b/test_regress/t/t_dfg_unhandled.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +file_grep($Self->{stats}, qr/Optimizations, DFG pre inline Ast2Dfg, non-representable \(impure\)\s+(\d+)/i, 1); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_unhandled.v b/test_regress/t/t_dfg_unhandled.v new file mode 100644 index 000000000..f01edfeff --- /dev/null +++ b/test_regress/t/t_dfg_unhandled.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk, + output wire [31:0] o0 + ); + + int file; + + assign o0 = $fgetc(file); // Impure + +endmodule diff --git a/test_regress/t/t_display.out b/test_regress/t/t_display.out index 375c5dfc9..0ecc886a1 100644 --- a/test_regress/t/t_display.out +++ b/test_regress/t/t_display.out @@ -37,7 +37,10 @@ [0] %P="sv-str" [0] %u=dcba %0u=dcba [0] %U=dcba %0U=dcba -[0] %D= 12 %d= 12 %01d=12 %06d=000012 %6d= 12 +[0] %D= 12 %d= 12 %01d=12 %06d=000012 %6d= 12 %-06d=12 %-6d=12 +[0] %X=00c %x=00c %01x=c %06x=00000c %6x=00000c %-06x=c %-6x=c +[0] %O=014 %o=014 %01o=14 %06o=000014 %6o=000014 %-06o=14 %-6o=14 +[0] %B=000001100 %b=000001100 %01b=1100 %06b=001100 %6b=001100 %-06b=1100 %-6b=1100 [0] %t= 0 %03t= 0 %0t=0 [0] %s=! %s= what! %s= hmmm!1234 diff --git a/test_regress/t/t_display.v b/test_regress/t/t_display.v index 0da4596e2..24aa8f428 100644 --- a/test_regress/t/t_display.v +++ b/test_regress/t/t_display.v @@ -120,8 +120,14 @@ module t; {"a","b","c","d"}, {"a","b","c","d"}); // Avoid binary output // %z is tested in t_sys_sformat.v - $display("[%0t] %%D=%D %%d=%d %%01d=%01d %%06d=%06d %%6d=%6d", $time, - nine, nine, nine, nine, nine); + $display("[%0t] %%D=%D %%d=%d %%01d=%01d %%06d=%06d %%6d=%6d %%-06d=%-06d %%-6d=%-6d", $time, + nine, nine, nine, nine, nine, nine, nine); + $display("[%0t] %%X=%X %%x=%x %%01x=%01x %%06x=%06x %%6x=%6x %%-06x=%-06x %%-6x=%-6x", $time, + nine, nine, nine, nine, nine, nine, nine); + $display("[%0t] %%O=%O %%o=%o %%01o=%01o %%06o=%06o %%6o=%6o %%-06o=%-06o %%-6o=%-6o", $time, + nine, nine, nine, nine, nine, nine, nine); + $display("[%0t] %%B=%B %%b=%b %%01b=%01b %%06b=%06b %%6b=%6b %%-06b=%-06b %%-6b=%-6b", $time, + nine, nine, nine, nine, nine, nine, nine); $display("[%0t] %%t=%t %%03t=%03t %%0t=%0t", $time, $time, $time, $time); $display; diff --git a/test_regress/t/t_display_cwide_bad.out b/test_regress/t/t_display_cwide_bad.out new file mode 100644 index 000000000..f054ce1b7 --- /dev/null +++ b/test_regress/t/t_display_cwide_bad.out @@ -0,0 +1,6 @@ +%Warning-WIDTH: t/t_display_cwide_bad.v:10:7: $display-like format of %c format of > 8 bit value + 10 | $display("%c", 32'h1234); + | ^~~~~~~~ + ... For warning description see https://verilator.org/warn/WIDTH?v=latest + ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_clocker_bad.pl b/test_regress/t/t_display_cwide_bad.pl similarity index 84% rename from test_regress/t/t_clocker_bad.pl rename to test_regress/t/t_display_cwide_bad.pl index 0628b9d18..a5846c699 100755 --- a/test_regress/t/t_clocker_bad.pl +++ b/test_regress/t/t_display_cwide_bad.pl @@ -2,7 +2,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2004 by Wilson Snyder. This program is free software; you +# Copyright 2003 by Wilson Snyder. This program is free software; you # can redistribute it and/or modify it under the terms of either the GNU # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. @@ -10,8 +10,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -top_filename("t/t_clocker.v"); - lint( fails => 1, expect_filename => $Self->{golden_filename}, diff --git a/test_regress/t/t_display_cwide_bad.v b/test_regress/t/t_display_cwide_bad.v new file mode 100644 index 000000000..0cd07beb4 --- /dev/null +++ b/test_regress/t/t_display_cwide_bad.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + // Display formatting + $display("%c", 32'h1234); // Bad wide %c + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_dist_fixme.pl b/test_regress/t/t_dist_fixme.pl index f722de3fe..74c927883 100755 --- a/test_regress/t/t_dist_fixme.pl +++ b/test_regress/t/t_dist_fixme.pl @@ -38,7 +38,8 @@ if (!-r "$root/.git") { print "$grep\n"; foreach my $line (split /\n/, $grep) { print "L $line\n"; - $names{$1} = 1 if $line =~ /^([^:]+)/; + # FIXMEV5 for use in develop-v5 branch until merged to master + $names{$1} = 1 if $line =~ /^([^:]+)/ && $line !~ /FIXMEV5/; } } } diff --git a/test_regress/t/t_dist_warn_coverage.pl b/test_regress/t/t_dist_warn_coverage.pl new file mode 100755 index 000000000..52cf64281 --- /dev/null +++ b/test_regress/t/t_dist_warn_coverage.pl @@ -0,0 +1,246 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +use IO::File; +use strict; +use vars qw($Self); + +scenarios(dist => 1); + +my $root = ".."; +my $Debug = $Self->{verbose}; +my %Messages; +my %Outputs; + +my %Suppressed; +foreach my $s ( + ' exited with ', # driver.pl filters out + 'EOF in unterminated string', # Instead get normal unterminated + # Not yet analyzed + ' is not an in/out/inout/param/interface: ', + ' loading non-variable', + '$fopen mode should be <= 4 characters', + '\'foreach\' loop variable expects simple variable name', + '--coverage and --savable not supported together', + '--pipe-filter protocol error, unexpected: ', + '/*verilator sformat*/ can only be applied to last argument of ', + 'Argument needed for string.', + 'Array initialization has too few elements, need element ', + 'Assert not allowed under another assert', + 'Assigned pin is neither input nor output', + 'Assignment pattern key used multiple times: ', + 'Assignment pattern with no members', + 'Assignment pattern with too many elements', + 'Attempted parameter setting of non-parameter: Param ', + 'Attempting to extend using a non-class ', + 'Can\'t find varpin scope of ', + 'Can\'t resolve module reference: \'', + 'Cannot mix DPI import, DPI export, class methods, and/or public ', + 'Cannot write preprocessor output: ', + 'Circular logic when ordering code (non-cutable edge loop)', + 'Deferred assertions must use \'#0\' (IEEE 1800-2017 16.4)', + 'Define or directive not defined: `', + 'Duplicate declaration of member name: ', + 'EOF in (*', + 'Enum names without values only allowed on numeric types', + 'Enum ranges must be integral, per spec', + 'Exceeded limit of ', + 'Extern declaration\'s scope is not a defined class', + 'Format to $display-like function must have constant format string', + 'Forward typedef used as class/package does not resolve to class/package: ', + 'Illegal +: or -: select; type already selected, or bad dimension: ', + 'Illegal bit or array select; type already selected, or bad dimension: ', + 'Illegal range select; type already selected, or bad dimension: ', + 'In defparam, instance ', + 'Interface port ', + 'Member selection of non-struct/union object \'', + 'Modport item is not a function/task: ', + 'Modport item is not a variable: ', + 'Modport item not found: ', + 'Modport not referenced as .', + 'Modport not referenced from underneath an interface: ', + 'Multiple \'{ default: } clauses', + 'Non-interface used as an interface: ', + 'Not marked as function return var', + 'Parameter not found in sub-module: Param ', + 'Parameter type pin value isn\'t a type: Param ', + 'Parameter type variable isn\'t a type: Param ', + 'Pattern replication value of 0 is not legal.', + 'Return with return value isn\'t underneath a function', + 'Select from non-array ', + 'Signals inside functions/tasks cannot be marked forceable', + 'Size-changing cast to zero or negative size', + 'Slice size cannot be zero.', + 'Slices of arrays in assignments have different unpacked dimensions, ', + 'String of ', + 'Symbol matching ', + 'Syntax Error: Range \':\', \'+:\' etc are not allowed in the instance ', + 'Syntax error parsing real: \'', + 'Syntax error: \'virtual\' not allowed before var declaration', + 'This may be because there\'s no search path specified with -I.', + 'Unexpected connection to arrayed port', + 'Unhandled attribute type', + 'Unknown Error Code: ', + 'Unknown `pragma', + 'Unknown built-in event method ', + 'Unsized numbers/parameters not allowed in streams.', + 'Unsupported LHS tristate construct: ', + 'Unsupported RHS tristate construct: ', + 'Unsupported or syntax error: Unsized range in instance or other declaration', + 'Unsupported pullup/down (weak driver) construct.', + 'Unsupported tristate construct (not in propagation graph): ', + 'Unsupported tristate port expression: ', + 'Unsupported/Illegal: Assignment pattern', + 'Unsupported/unknown built-in dynamic array method ', + 'Unsupported: $bits for queue', + 'Unsupported: $c can\'t generate wider than 64 bits', + 'Unsupported: %l in $fscanf', + 'Unsupported: %m in $fscanf', + 'Unsupported: --no-structs-packed', + 'Unsupported: 4-state numbers in this context', + 'Unsupported: Concatenation to form ', + 'Unsupported: Non-variable on LHS of built-in method \'', + 'Unsupported: Only one PSL clock allowed per assertion', + 'Unsupported: Per-bit array instantiations ', + 'Unsupported: Public functions with >64 bit outputs; ', + 'Unsupported: RHS of ==? or !=? must be ', + 'Unsupported: Ranges ignored in port-lists', + 'Unsupported: Replication to form ', + 'Unsupported: Shifting of by over 32-bit number isn\'t supported.', + 'Unsupported: Signal strengths are unsupported ', + 'Unsupported: Size-changing cast on non-basic data type', + 'Unsupported: Slice of non-constant bounds', + 'Unsupported: Unclocked assertion', + 'Unsupported: don\'t know how to deal with ', + 'Unsupported: event arrays', + 'Unsupported: left < right of bit extract: ', + 'Unsupported: modport export', + 'Unsupported: static cast to ', + 'Unsupported: super', + 'Width of :+ or :- is < 0: ', + 'Width of :+ or :- is huge; vector of over 1billion bits: ', + 'Width of bit extract isn\'t a constant', + 'Width of bit range is huge; vector of over 1billion bits: 0x', + 'dynamic new() not expected in this context (data type must be dynamic array)', + 'dynamic new() not expected in this context (expected under an assign)', + 'line_length must be multiple of 4 for BASE64', + 'new() not expected in this context', + 'no_inline not supported for tasks', + ) { $Suppressed{$s} = 1; } + +if (!-r "$root/.git") { + skip("Not in a git repository"); +} else { + check(); +} + +ok(1); +1; + +sub check { + read_messages(); + read_outputs(); + + print "Number of suppressions = ", scalar(keys %Suppressed), "\n"; + print "Coverage = ", 100 - int(100 * scalar(keys %Suppressed) / scalar(keys %Messages)), "%\n"; + print "\n"; + + print "Checking for v3error/v3warn messages in sources without coverage in test_regress/t/*.out:\n"; + print "\n"; + + my %used_suppressed; + msg: + for my $msg (sort {$Messages{$a}{fileline} cmp $Messages{$b}{fileline}} keys %Messages) { + my $fileline = $Messages{$msg}{fileline}; + for my $output (keys %Outputs) { + if (index($output, $msg) != -1) { + # print "$fileline: M '$msg' HIT '$output'\n"; + next msg; + } + } + # Some exceptions + next msg if ($msg =~ /internal:/i); + + my $line = $Messages{$msg}{line}; + chomp $line; + $line =~ s/^\s+//; + + if (%Suppressed{$msg}) { + $used_suppressed{$msg} = 1; + print "$fileline: Suppressed check for message in source: '$msg'\n" if $Debug; + } else { + error("$fileline: Missing test_regress/t/*.out test for message in source: '$msg'"); + print(" Line is: ", $line, "\n") if $Debug; + } + } + + for my $msg (sort keys %Suppressed) { + if (!$used_suppressed{$msg}) { + print "Suppression not used: '$msg'\n"; + } + } +} + +sub read_messages { + foreach my $filename (glob "$root/src/*") { + my $fh = IO::File->new("<$filename") + or error("$! $filename"); + my $lineno = 0; + my $read_next; + line: + while (my $origline = ($fh && $fh->getline)) { + my $line = $origline; + ++$lineno; + if ($line =~ /\b(v3error|v3warn)\b\($/g) { + $read_next = 1 if $line !~ /LCOV_EXCL_LINE/; + next line; + } + if ($line =~ s/.*\b(v3error|v3warn)\b//g) { + $read_next = 1 if $line !~ /LCOV_EXCL_LINE/; + } + if ($read_next) { + $read_next = 0; + next if $line =~ /LCOV_EXCL_LINE/; + next if $line =~ /\\/; # \" messes up next part + if ($line =~ /"([^"]*)"/) { + my $msg = $1; + my $fileline = $filename . ":" . $lineno; + # print "FFFF $fileline: $msg LL $line\n"; + $Messages{$msg}{fileline} = $fileline; + $Messages{$msg}{line} = $origline; + } + } + } + } + print "Number of messages = ",scalar(keys %Messages), "\n"; +} + +sub read_outputs { + file: + foreach my $filename (glob ("$root/test_regress/t/*.pl" + . " $root/test_regress/t/*.out" + . " $root/docs/gen/*.rst")) { + next if $filename =~ /t_dist_warn_coverage/; # Avoid our own suppressions + my $fh = IO::File->new("<$filename") + or error("$! $filename"); + while (my $line = ($fh && $fh->getline)) { + if ($line =~ /^\$date/) { + # Assume it is a VCD file + next file; + } + $Outputs{$line} = 1; + } + } + print "Number of outputs = ",scalar(keys %Outputs), "\n"; +} + +# Local Variables: +# compile-command:"./t_dist_warn_coverage.pl" +# End: diff --git a/test_regress/t/t_do_not_convert_to_comb.pl b/test_regress/t/t_do_not_convert_to_comb.pl new file mode 100755 index 000000000..c6db81d38 --- /dev/null +++ b/test_regress/t/t_do_not_convert_to_comb.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +# We must not convert these blocks into combinational blocks +file_grep($Self->{stats}, qr/Scheduling, size of class: combinational\s+(\d+)/i, 0); + +ok(1); +1; diff --git a/test_regress/t/t_do_not_convert_to_comb.v b/test_regress/t/t_do_not_convert_to_comb.v new file mode 100644 index 000000000..f80ce544b --- /dev/null +++ b/test_regress/t/t_do_not_convert_to_comb.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t ( + clk, + input wire i, + output reg o_0, + output reg o_1, + output reg o_2, + output reg o_3, + output reg o_4, + output reg o_5 + ); + + input clk; + + reg a = 0; + reg b = 0; + + event e; + + // We must not convert these blocks into combinational blocks + + always @(i) begin + a <= ~a; + o_0 = i; + end + + always @(i) begin + force b = 1; + o_1 = i; + end + + always @(i) begin + release b; + o_2 = i; + end + + always @(i) begin + -> e; + o_3 = i; + end + + always @(i) begin + ->> e; + o_4 = i; + end + + always @(i) begin + $display("Hello"); + o_5 = i; + end + +endmodule diff --git a/test_regress/t/t_dump_dfg.pl b/test_regress/t/t_dump_dfg.pl new file mode 100755 index 000000000..2164a894c --- /dev/null +++ b/test_regress/t/t_dump_dfg.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +# For code coverage of graph dumping, so does not matter much what the input is +top_filename("t/t_bench_mux4k.v"); + +compile( + verilator_flags2 => ["--dump-dfg", "--dumpi-dfg 9"], + ); + +ok(1); +1; diff --git a/test_regress/t/t_dump_tree_dot.pl b/test_regress/t/t_dump_tree_dot.pl new file mode 100755 index 000000000..8caebed51 --- /dev/null +++ b/test_regress/t/t_dump_tree_dot.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +top_filename("t/t_EXAMPLE.v"); + +lint( + v_flags => ["--lint-only --dump-tree-dot"], + ); + +ok(1); +1; diff --git a/test_regress/t/t_enum_bad_dup.out b/test_regress/t/t_enum_bad_dup.out new file mode 100644 index 000000000..b6dac7521 --- /dev/null +++ b/test_regress/t/t_enum_bad_dup.out @@ -0,0 +1,7 @@ +%Error: t/t_enum_bad_dup.v:10:19: Duplicate declaration of enum value: DUP_VALUE + 10 | DUP_VALUE = 3 + | ^~~~~~~~~ + t/t_enum_bad_dup.v:9:19: ... Location of original declaration + 9 | typedef enum { DUP_VALUE = 2, + | ^~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_bad_dup.pl b/test_regress/t/t_enum_bad_dup.pl new file mode 100755 index 000000000..f1eef1686 --- /dev/null +++ b/test_regress/t/t_enum_bad_dup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only -Wwarn-VARHIDDEN"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_enum_bad_dup.v b/test_regress/t/t_enum_bad_dup.v new file mode 100644 index 000000000..94874b91d --- /dev/null +++ b/test_regress/t/t_enum_bad_dup.v @@ -0,0 +1,13 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + + typedef enum { DUP_VALUE = 2, + DUP_VALUE = 3 + } dup_t; + +endmodule diff --git a/test_regress/t/t_enum_bad_wrap.out b/test_regress/t/t_enum_bad_wrap.out new file mode 100644 index 000000000..72c9403fc --- /dev/null +++ b/test_regress/t/t_enum_bad_wrap.out @@ -0,0 +1,5 @@ +%Error: t/t_enum_bad_wrap.v:11:19: Enum value illegally wrapped around (IEEE 1800-2017 6.19) + : ... In instance t + 11 | WRAPPED + | ^~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_bad_wrap.pl b/test_regress/t/t_enum_bad_wrap.pl new file mode 100755 index 000000000..f1eef1686 --- /dev/null +++ b/test_regress/t/t_enum_bad_wrap.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only -Wwarn-VARHIDDEN"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_enum_bad_wrap.v b/test_regress/t/t_enum_bad_wrap.v new file mode 100644 index 000000000..9219f08e8 --- /dev/null +++ b/test_regress/t/t_enum_bad_wrap.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + + typedef enum [1:0] { + PREWRAP = 2'd3, + WRAPPED + } wrap_t; + +endmodule diff --git a/test_regress/t/t_enum_type_nomethod_bad.out b/test_regress/t/t_enum_type_nomethod_bad.out new file mode 100644 index 000000000..bc6d41978 --- /dev/null +++ b/test_regress/t/t_enum_type_nomethod_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_enum_type_nomethod_bad.v:15:9: Unknown built-in enum method 'bad_no_such_method' + : ... In instance t + 15 | e.bad_no_such_method(); + | ^~~~~~~~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_type_nomethod_bad.pl b/test_regress/t/t_enum_type_nomethod_bad.pl new file mode 100755 index 000000000..3c938d615 --- /dev/null +++ b/test_regress/t/t_enum_type_nomethod_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename} + ); + +ok(1); +1; diff --git a/test_regress/t/t_enum_type_nomethod_bad.v b/test_regress/t/t_enum_type_nomethod_bad.v new file mode 100644 index 000000000..c4aceee41 --- /dev/null +++ b/test_regress/t/t_enum_type_nomethod_bad.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + typedef enum [3:0] { + E01 = 1 + } my_t; + + my_t e; + + initial begin + e.bad_no_such_method(); + $stop; + end + +endmodule diff --git a/test_regress/t/t_event.v b/test_regress/t/t_event.v index 9bf376779..566657dd0 100644 --- a/test_regress/t/t_event.v +++ b/test_regress/t/t_event.v @@ -33,49 +33,44 @@ module t(/*AUTOARG*/ always @(e2) begin `WRITE_VERBOSE(("[%0t] e2\n", $time)); + if (!e2.triggered) $stop; last_event[2] = 1; end always @(posedge clk) begin `WRITE_VERBOSE(("[%0t] cyc=%0d last_event=%5b\n", $time, cyc, last_event)); cyc <= cyc + 1; - if (cyc == 1) begin - // Check no initial trigger - if (last_event != 0) $stop; - end - // - else if (cyc == 10) begin - last_event = 0; - -> e1; - end - else if (cyc == 12) begin - if (last_event != 32'b10) $stop; - last_event = 0; - end - else if (cyc == 13) begin - // Check not still triggering - if (last_event != 0) $stop; - last_event = 0; - end - // - else if (cyc == 10) begin - last_event = 0; - ->> e2; - end - else if (cyc == 12) begin - if (last_event != 32'b100) $stop; - last_event = 0; - end - else if (cyc == 13) begin - // Check not still triggering - if (last_event != 0) $stop; - last_event = 0; - end - // - else if (cyc == 99) begin - $write("*-* All Finished *-*\n"); - $finish; - end + case (cyc) + default: begin + // Check no initial or spurious trigger + if (last_event != 0) $stop; + end + // + 10: begin + if (last_event != 0) $stop; + -> e1; + if (!e1.triggered) $stop; + end + 11: begin + if (last_event != 32'b10) $stop; + last_event = 0; + end + // + 13: begin + if (last_event != 0) $stop; + ->> e2; + if (e2.triggered) $stop; + end + 14: begin + if (last_event != 32'b100) $stop; + last_event = 0; + end + // + 99: begin + $write("*-* All Finished *-*\n"); + $finish; + end + endcase end endmodule diff --git a/test_regress/t/t_event_control.out b/test_regress/t/t_event_control.out new file mode 100644 index 000000000..f04260a95 --- /dev/null +++ b/test_regress/t/t_event_control.out @@ -0,0 +1,12 @@ +%Error-NOTIMING: t/t_event_control.v:14:7: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 14 | @(clk); + | ^ + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error-NOTIMING: t/t_event_control.v:16:7: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 16 | @(clk); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_event_control.pl b/test_regress/t/t_event_control.pl new file mode 100755 index 000000000..1047a4907 --- /dev/null +++ b/test_regress/t/t_event_control.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ['--no-timing'], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_event_control_unsup.v b/test_regress/t/t_event_control.v similarity index 100% rename from test_regress/t/t_event_control_unsup.v rename to test_regress/t/t_event_control.v diff --git a/test_regress/t/t_event_control_expr.pl b/test_regress/t/t_event_control_expr.pl new file mode 100755 index 000000000..a50ea7293 --- /dev/null +++ b/test_regress/t/t_event_control_expr.pl @@ -0,0 +1,33 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + # do not test classes for multithreaded, as V3InstrCount doesn't handle MemberSel + verilator_flags2 => $Self->{vltmt} ? ['-DNO_CLASS'] : [], + ); + +execute( + check_finished => 1, + ); + +for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}*.cpp")) { + # Check that these simple expressions are not stored in temp variables + file_grep_not($file, qr/__Vtrigcurr__expression_.* = vlSelf->clk;/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = vlSelf->t__DOT__q.at\(0U\);/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSelf->t__DOT____Vcellinp__u_array__t/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSymsp->TOP__t__DOT__u_class.__PVT__obj/); + # The line below should only be generated if concats/replicates aren't converted to separate senitems + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSelf->t__DOT__a/); +} + +ok(1); +1; diff --git a/test_regress/t/t_event_control_expr.v b/test_regress/t/t_event_control_expr.v new file mode 100644 index 000000000..23fc5bc55 --- /dev/null +++ b/test_regress/t/t_event_control_expr.v @@ -0,0 +1,174 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +`define STRINGIFY(text) `"text`" + +//======================================================================== +// Various expression tests. The macro generates a module with the desired +// input and tested expression. +// +`define EXPR_TEST(name, test_edges, inputs, expr) \ +module t_``name inputs; \ + logic[$bits(expr)-1:0] last = 0; \ + always @(expr) begin \ + if ($bits(expr) > 1) begin \ + `WRITE_VERBOSE(("[%0t] %s [changed] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + end \ + if ($time > 0 && (expr) == last) $stop; \ + last <= expr; \ + end \ + generate if (test_edges) begin \ + always @(posedge expr) begin \ + `WRITE_VERBOSE(("[%0t] %s [posedge] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + if ($time > 0 && ({1'b0, ~(expr)}[0] || last[0])) $stop; \ + end \ + always @(negedge expr) begin \ + `WRITE_VERBOSE(("[%0t] %s [negedge] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + if ($time > 0 && ({1'b0, expr}[0] || ~last[0])) $stop; \ + end \ + end endgenerate \ +endmodule + +`EXPR_TEST(xor, 1, (input a, b), b^a) +`EXPR_TEST(nand, 1, (input a, b, c), ~(c&b&a)) +`EXPR_TEST(concat1, 1, (input a, b, c), {{a, b},c,a,{2{a,b,c}}}) +`EXPR_TEST(reduce, 1, (input[3:0] v), v[0]^v[1]^v[2]^v[3]) +`EXPR_TEST(concat2, 1, (input[3:0] v), {{v[0]|v[1]},v[1]|v[2],{4{v[2]|v[3]}}}) +`EXPR_TEST(add, 0, (input int i, j), i+j) +`EXPR_TEST(lt, 1, (input int i, j), i 0 && expr == last) $stop; \ + last <= expr; \ + end \ +endmodule + +`CLASS_TEST(class, obj.k) + +`ifdef UNSUP +`CLASS_TEST(method, obj.get_k()) +`endif +`endif + +//======================================================================== +// $c test has to be written out explicitly as the STRINGIFY macro can't handle it +// +module t_cstmt; + logic last = 0; + always @($c("vlSelf->clk")) begin + if ($time > 0 && logic'($c("vlSelf->clk")) == last) $stop; + last <= logic'($c("vlSelf->clk")); + end + always @(posedge $c("vlSelf->clk")) begin + `WRITE_VERBOSE(("[%0t] cstmt [posedge] $c(\"vlSelf->clk\")=%0b, last=%b\n", $time, $c("vlSelf->clk"), last)); + if ($time > 0 && (~logic'($c("vlSelf->clk")) || last)) $stop; + end + always @(negedge $c("vlSelf->clk")) begin + `WRITE_VERBOSE(("[%0t] cstmt [negedge] $c(\"vlSelf->clk\")=%0b, last=%b\n", $time, $c("vlSelf->clk"), last)); + if ($time > 0 && (logic'($c("vlSelf->clk")) || !last)) $stop; + end +endmodule + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic a = 0, b = 0, c = 0; + t_xor u_xor(.*); + t_nand u_nand(.*); + t_concat1 u_concat1(.*); + + logic[3:0] v = '0; + t_reduce u_reduce(.*); + t_concat2 u_concat2(.*); + + int i = 0, j = 0; + t_add u_add(.*); + t_lt u_lt(.*); + + int t[5] = {0, 1, 2, 3, 4}; + t_array u_array(.*); + t_array_complex u_array_complex(.*); + + int q[$]; + t_queue u_queue(.*); + t_queue_mul u_queue_mul(.*); + +`ifdef UNSUP + t_func u_func(.*); +`endif + + int k; + assign k = i + j; + `ifndef NO_CLASS + t_class u_class(.*); +`ifdef UNSUP + t_method u_method(.*); +`endif + `endif + + t_cstmt u_cstmt; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + // a, b, c + a <= ~a; + if (cyc % 2 == 0) b <= ~b; + else c <= ~c; + // v + if (cyc % 3 == 0) v[0] <= 1; + else v <= v << 1; + // i, j + i <= i + 2; + if (cyc % 2 == 0) j <= j + 4; + // t + t[cyc % 5] <= t[cyc % 5] + cyc; + // q + q.push_front(cyc); + `WRITE_VERBOSE(("[%0t] values: clk=%b, cyc=%0d, a=%b, b=%b, v=%b, i=%0x, j=%0x, t=[%0x, %0x, %0x, %0x, %0x], obj.k=%0x\n", + $time, clk, cyc, a, b, v, i, j, t[0], t[1], t[2], t[3], t[4], k)); + `WRITE_VERBOSE((" q=%p\n", q)); + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_event_control_expr_unsup.out b/test_regress/t/t_event_control_expr_unsup.out new file mode 100644 index 000000000..8f8e2cb64 --- /dev/null +++ b/test_regress/t/t_event_control_expr_unsup.out @@ -0,0 +1,9 @@ +%Error-UNSUPPORTED: t/t_event_control_expr.v:55:13: Unsupported: function calls in sensitivity lists + 55 | always @(id(cyc)) begin + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_event_control_expr.v:82:17: Unsupported: function calls in sensitivity lists + : ... In instance t.u_method + 82 | always @(obj.get_k()) begin + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_event_control_expr_unsup.pl b/test_regress/t/t_event_control_expr_unsup.pl new file mode 100755 index 000000000..1bf0030aa --- /dev/null +++ b/test_regress/t/t_event_control_expr_unsup.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); # no vltmt, as AstMemberSel is unhandled in V3InstrCount + +top_filename("t_event_control_expr.v"); + +compile( + verilator_flags2 => ['-DUNSUP'], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_event_control_timing.out b/test_regress/t/t_event_control_timing.out new file mode 100644 index 000000000..35690cce5 --- /dev/null +++ b/test_regress/t/t_event_control_timing.out @@ -0,0 +1,3 @@ +[10] Got +[15] Got +*-* All Finished *-* diff --git a/test_regress/t/t_event_control_timing.pl b/test_regress/t/t_event_control_timing.pl new file mode 100755 index 000000000..6a4829740 --- /dev/null +++ b/test_regress/t/t_event_control_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_event_control.v"); + + compile( + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_event_control_unsup.out b/test_regress/t/t_event_control_unsup.out deleted file mode 100644 index 7c40c182e..000000000 --- a/test_regress/t/t_event_control_unsup.out +++ /dev/null @@ -1,12 +0,0 @@ -%Error-UNSUPPORTED: t/t_event_control_unsup.v:14:7: Unsupported: event control statement in this location - : ... In instance t - : ... Suggest have one event control statement per procedure, at the top of the procedure - 14 | @(clk); - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_event_control_unsup.v:16:7: Unsupported: event control statement in this location - : ... In instance t - : ... Suggest have one event control statement per procedure, at the top of the procedure - 16 | @(clk); - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_extract_static_const.v b/test_regress/t/t_extract_static_const.v index 2a49caced..5e258dbe9 100755 --- a/test_regress/t/t_extract_static_const.v +++ b/test_regress/t/t_extract_static_const.v @@ -27,14 +27,14 @@ module t (/*AUTOARG*/); initial begin // Note: Base index via $c to prevent optimizatoin by Verilator - $display("0x%32x", C[$c(0*32)+:32]); - $display("0x%32x", D[$c(1*32)+:32]); - $display("0x%32x", C[$c(2*32)+:32]); - $display("0x%32x", D[$c(3*32)+:32]); - $display("0x%32x", C[$c(4*32)+:32]); - $display("0x%32x", D[$c(5*32)+:32]); - $display("0x%32x", C[$c(6*32)+:32]); - $display("0x%32x", D[$c(7*32)+:32]); + $display("0x%8x", C[$c(0*32)+:32]); + $display("0x%8x", D[$c(1*32)+:32]); + $display("0x%8x", C[$c(2*32)+:32]); + $display("0x%8x", D[$c(3*32)+:32]); + $display("0x%8x", C[$c(4*32)+:32]); + $display("0x%8x", D[$c(5*32)+:32]); + $display("0x%8x", C[$c(6*32)+:32]); + $display("0x%8x", D[$c(7*32)+:32]); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_extract_static_const_multimodule.v b/test_regress/t/t_extract_static_const_multimodule.v index 0f959991a..7885ecb5b 100755 --- a/test_regress/t/t_extract_static_const_multimodule.v +++ b/test_regress/t/t_extract_static_const_multimodule.v @@ -29,11 +29,11 @@ module a( trig_o <= 1'd0; if (trig_i) begin // Note: Base index via $c to prevent optimizatoin by Verilator - $display("0x%32x", C[$c(0*32)+:32]); - $display("0x%32x", C[$c(2*32)+:32]); - $display("0x%32x", C[$c(4*32)+:32]); - $display("0x%32x", C[$c(6*32)+:32]); - $display("0x%256x", C); + $display("0x%8x", C[$c(0*32)+:32]); + $display("0x%8x", C[$c(2*32)+:32]); + $display("0x%8x", C[$c(4*32)+:32]); + $display("0x%8x", C[$c(6*32)+:32]); + $display("0x%32x", C); trig_o <= 1'd1; end end @@ -61,11 +61,11 @@ module b( trig_o <= 1'd0; if (trig_i) begin // Note: Base index via $c to prevent optimizatoin by Verilator - $display("0x%32x", C[$c(1*32)+:32]); - $display("0x%32x", C[$c(3*32)+:32]); - $display("0x%32x", C[$c(5*32)+:32]); - $display("0x%32x", C[$c(7*32)+:32]); - $display("0x%256x", C); + $display("0x%8x", C[$c(1*32)+:32]); + $display("0x%8x", C[$c(3*32)+:32]); + $display("0x%8x", C[$c(5*32)+:32]); + $display("0x%8x", C[$c(7*32)+:32]); + $display("0x%32x", C); trig_o <= 1'd1; end end diff --git a/test_regress/t/t_flag_binary.pl b/test_regress/t/t_flag_binary.pl new file mode 100755 index 000000000..c8f265aa1 --- /dev/null +++ b/test_regress/t/t_flag_binary.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2019 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_flag_main.v"); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_flag_comp_limit_parens.pl b/test_regress/t/t_flag_comp_limit_parens.pl index 51fb7f932..d978e33fa 100755 --- a/test_regress/t/t_flag_comp_limit_parens.pl +++ b/test_regress/t/t_flag_comp_limit_parens.pl @@ -18,7 +18,8 @@ execute( check_finished => 1, ); -file_grep(glob_one("$Self->{obj_dir}/Vt_flag_comp_limit_parens___024root__DepSet_*__0__Slow.cpp"), qr/Vdeeptemp/x); +my @files = glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet*__Slow.cpp"); +file_grep_any(\@files, qr/Vdeeptemp/i); ok(1); 1; diff --git a/test_regress/t/t_flag_context_bad.out b/test_regress/t/t_flag_context_bad.out index bfe90fd33..13052d8dd 100644 --- a/test_regress/t/t_flag_context_bad.out +++ b/test_regress/t/t_flag_context_bad.out @@ -2,6 +2,6 @@ : ... In instance t ... For warning description see https://verilator.org/warn/WIDTH?v=latest ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. -%Warning-UNUSED: t/t_flag_context_bad.v:9:15: Signal is not used: 'foo' - : ... In instance t +%Warning-UNUSEDSIGNAL: t/t_flag_context_bad.v:9:15: Signal is not used: 'foo' + : ... In instance t %Error: Exiting due to diff --git a/test_regress/t/t_flag_csplit_eval.pl b/test_regress/t/t_flag_csplit_eval.pl index 325f38067..3b44c3193 100755 --- a/test_regress/t/t_flag_csplit_eval.pl +++ b/test_regress/t/t_flag_csplit_eval.pl @@ -15,14 +15,14 @@ sub check_evals { local $/; undef $/; my $wholefile = <$fh>; - if ($wholefile =~ /___eval__[0-9]+\(.*\)\s*{/) { + if ($wholefile =~ /__eval_nba__[0-9]+\(.*\)\s*{/) { ++$got; } } $got >= 2 or error("Too few _eval functions found: $got"); } -scenarios(vlt_all => 1); +scenarios(vlt => 1); compile( v_flags2 => ["--output-split 1 --output-split-cfuncs 20"], diff --git a/test_regress/t/t_flag_deprecated_bad.out b/test_regress/t/t_flag_deprecated_bad.out new file mode 100644 index 000000000..e13b7628f --- /dev/null +++ b/test_regress/t/t_flag_deprecated_bad.out @@ -0,0 +1,7 @@ +%Warning-DEPRECATED: Option -O is deprecated. Use -f or -fno- instead. + ... For warning description see https://verilator.org/warn/DEPRECATED?v=latest + ... Use "/* verilator lint_off DEPRECATED */" and lint_on around source to disable this message. +%Warning-DEPRECATED: Option --prof-threads is deprecated. Use --prof-exec and --prof-pgo instead. +%Warning-DEPRECATED: Option --trace-fst-thread is deprecated. Use --trace-fst with --trace-threads > 0. +%Warning-DEPRECATED: Option order-clock-delay is deprecated and has no effect. +%Error: Exiting due to diff --git a/test_regress/t/t_flag_deprecated_bad.pl b/test_regress/t/t_flag_deprecated_bad.pl new file mode 100755 index 000000000..5edf03bb9 --- /dev/null +++ b/test_regress/t/t_flag_deprecated_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ["-Ox --prof-threads --trace-fst-thread --order-clock-delay"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_deprecated_bad.v b/test_regress/t/t_flag_deprecated_bad.v new file mode 100644 index 000000000..582a47b4a --- /dev/null +++ b/test_regress/t/t_flag_deprecated_bad.v @@ -0,0 +1,8 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); +endmodule diff --git a/test_regress/t/t_flag_expand_limit.pl b/test_regress/t/t_flag_expand_limit.pl index 6270f76ca..60ff7ed44 100755 --- a/test_regress/t/t_flag_expand_limit.pl +++ b/test_regress/t/t_flag_expand_limit.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); compile( - verilator_flags2 => ['--expand-limit 1 --stats'], + verilator_flags2 => ['--expand-limit 1 --stats -fno-dfg'], ); file_grep($Self->{stats}, qr/Optimizations, expand limited\s+(\d+)/i, 4); diff --git a/test_regress/t/t_flag_f_bad_cmt.out b/test_regress/t/t_flag_f_bad_cmt.out new file mode 100644 index 000000000..b214fc6c3 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.out @@ -0,0 +1,2 @@ +%Error: Unterminated /* comment inside -f file. +%Error: Exiting due to diff --git a/test_regress/t/t_flag_f_bad_cmt.pl b/test_regress/t/t_flag_f_bad_cmt.pl new file mode 100755 index 000000000..24d8b0de5 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +lint( + v_flags2 => ["-f t/t_flag_f_bad_cmt.vc"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_f_bad_cmt.v b/test_regress/t/t_flag_f_bad_cmt.v new file mode 100644 index 000000000..e5ec47252 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`include "t_flag_f_tsub_inc.v" + +module t; + initial begin +`ifndef GOT_DEF1 + $write("%%Error: NO GOT_DEF1\n"); $stop; +`endif +`ifndef GOT_DEF2 + $write("%%Error: NO GOT_DEF2\n"); $stop; +`endif +`ifndef GOT_DEF3 + $write("%%Error: NO GOT_DEF3\n"); $stop; +`endif +`ifndef GOT_DEF4 + $write("%%Error: NO GOT_DEF4\n"); $stop; +`endif +`ifndef GOT_DEF5 + $write("%%Error: NO GOT_DEF5\n"); $stop; +`endif +`ifndef GOT_DEF6 + $write("%%Error: NO GOT_DEF6\n"); $stop; +`endif +`ifdef NON_DEF + $write("%%Error: NON_DEF\n"); $stop; +`endif + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_flag_f_bad_cmt.vc b/test_regress/t/t_flag_f_bad_cmt.vc new file mode 100644 index 000000000..bc253b215 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.vc @@ -0,0 +1 @@ +/* Multiline unterminated comment diff --git a/test_regress/t/t_flag_noop_bad.out b/test_regress/t/t_flag_noop_bad.out index 3f89e26a3..d7bd1b71c 100644 --- a/test_regress/t/t_flag_noop_bad.out +++ b/test_regress/t/t_flag_noop_bad.out @@ -1 +1 @@ -%Error: verilator: Need --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, --xml-only or --E option +%Error: verilator: Need --binary, --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, --xml-only or --E option diff --git a/test_regress/t/t_flag_supported.pl b/test_regress/t/t_flag_supported.pl new file mode 100755 index 000000000..3effe6003 --- /dev/null +++ b/test_regress/t/t_flag_supported.pl @@ -0,0 +1,42 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if ($Self->have_coroutines) { + run( + cmd => ["../bin/verilator --get-supported COROUTINES"], + expect => '1 +', + logfile => "$Self->{obj_dir}/vlt_coroutines.log", + verilator_run => 1, + ); +} + +if ($Self->have_sc) { + run( + cmd => ["../bin/verilator --get-supported SYSTEMC"], + expect => '1 +', + logfile => "$Self->{obj_dir}/vlt_systemc.log", + verilator_run => 1, + ); +} + +run( + cmd => ["../bin/verilator --get-supported DOES_NOT_EXIST"], + expect => '', + logfile => "$Self->{obj_dir}/vlt_does_not_exist.log", + verilator_run => 1, + ); + + +ok(1); +1; diff --git a/test_regress/t/t_flag_values_bad.out b/test_regress/t/t_flag_values_bad.out new file mode 100644 index 000000000..8febf43ce --- /dev/null +++ b/test_regress/t/t_flag_values_bad.out @@ -0,0 +1,4 @@ +%Error: --output-split-cfuncs must be >= 0: -1 +%Error: --output-split-ctrace must be >= 0: -1 +%Error: --reloop-limit must be >= 2: -1 +%Error: Exiting due to diff --git a/test_regress/t/t_flag_values_bad.pl b/test_regress/t/t_flag_values_bad.pl new file mode 100755 index 000000000..a5f3d7743 --- /dev/null +++ b/test_regress/t/t_flag_values_bad.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ["--output-split-cfuncs -1", + "--output-split-ctrace -1", + "--reloop-limit -1",], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_xinitial_unique.pl b/test_regress/t/t_flag_xinitial_unique.pl index f2451bd3f..3f6077c00 100755 --- a/test_regress/t/t_flag_xinitial_unique.pl +++ b/test_regress/t/t_flag_xinitial_unique.pl @@ -18,7 +18,8 @@ execute( check_finished => 1, ); -file_grep(glob_one("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__0__Slow.cpp"), qr/VL_RAND_RESET/); +my @files = glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__Slow.cpp"); +file_grep_any(\@files, qr/VL_RAND_RESET/); ok(1); 1; diff --git a/test_regress/t/t_foreach_nindex_bad.out b/test_regress/t/t_foreach_nindex_bad.out new file mode 100644 index 000000000..1433bce46 --- /dev/null +++ b/test_regress/t/t_foreach_nindex_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_foreach_nindex_bad.v:12:34: foreach loop variables exceed number of indices of array + 12 | foreach (array[i, j, badk, badl]); + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_foreach_nindex_bad.pl b/test_regress/t/t_foreach_nindex_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_foreach_nindex_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_foreach_nindex_bad.v b/test_regress/t/t_foreach_nindex_bad.v new file mode 100644 index 000000000..d15e75e88 --- /dev/null +++ b/test_regress/t/t_foreach_nindex_bad.v @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + int array[2][2]; + + initial begin + foreach (array[i, j, badk, badl]); // bad + + $stop; + end + +endmodule diff --git a/test_regress/t/t_fork.out b/test_regress/t/t_fork.out index f0f3b3489..6070aeb85 100644 --- a/test_regress/t/t_fork.out +++ b/test_regress/t/t_fork.out @@ -1,6 +1,6 @@ -%Error-UNSUPPORTED: t/t_fork.v:10:14: Unsupported: fork statements - : ... In instance t +%Error-NOTIMING: t/t_fork.v:10:14: Fork statements require --timing + : ... In instance t 10 | fork : fblk | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest + ... For error description see https://verilator.org/warn/NOTIMING?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_fork.pl b/test_regress/t/t_fork.pl index a5846c699..9459580d1 100755 --- a/test_regress/t/t_fork.pl +++ b/test_regress/t/t_fork.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( + verilator_flags2 => ['--no-timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_fork_bbox.pl b/test_regress/t/t_fork_bbox.pl index 3a83673be..72f18add1 100755 --- a/test_regress/t/t_fork_bbox.pl +++ b/test_regress/t/t_fork_bbox.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( - verilator_flags2 => ['--lint-only --bbox-unsup'], + verilator_flags2 => ['--lint-only --no-timing --bbox-unsup'], ); ok(1); diff --git a/test_regress/t/t_fork_disable.out b/test_regress/t/t_fork_disable.out index 9c589b372..62ba6a34f 100644 --- a/test_regress/t/t_fork_disable.out +++ b/test_regress/t/t_fork_disable.out @@ -1,12 +1,8 @@ -%Error-UNSUPPORTED: t/t_fork_disable.v:12:7: Unsupported: fork statements - : ... In instance t - 12 | fork - | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_fork_disable.v:16:7: Unsupported: disable fork statements : ... In instance t 16 | disable fork; | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_fork_disable.v:17:7: Unsupported: wait fork statements : ... In instance t 17 | wait fork; diff --git a/test_regress/t/t_fork_disable.pl b/test_regress/t/t_fork_disable.pl index 89ffd046b..0ca21a5ca 100755 --- a/test_regress/t/t_fork_disable.pl +++ b/test_regress/t/t_fork_disable.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(linter => 1); lint( - verilator_flags2 => ['--lint-only'], + verilator_flags2 => ['--lint-only --timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_fork_label.pl b/test_regress/t/t_fork_label.pl index 1c4ba9485..8bb4480e1 100755 --- a/test_regress/t/t_fork_label.pl +++ b/test_regress/t/t_fork_label.pl @@ -10,7 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -compile(); +compile( + verilator_flags2 => ['--no-timing'], + ); execute(); diff --git a/test_regress/t/t_fork_label_timing.pl b/test_regress/t/t_fork_label_timing.pl new file mode 100755 index 000000000..8d67932b9 --- /dev/null +++ b/test_regress/t/t_fork_label_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_fork_label.v"); + + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_fork_timing.pl b/test_regress/t/t_fork_timing.pl new file mode 100755 index 000000000..f31aef7c3 --- /dev/null +++ b/test_regress/t/t_fork_timing.pl @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_fork.v"); + + compile( + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_func_check.v b/test_regress/t/t_func_check.v index d0f2a06f2..c35a17347 100644 --- a/test_regress/t/t_func_check.v +++ b/test_regress/t/t_func_check.v @@ -56,6 +56,8 @@ module chk (input clk, input rst_l, input expr); wire noxs = ((expr ^ expr) == 1'b0); + // FIXMEV5: this test is dodgy, noxs can be proven constant, so this block + // should never relly trigger... reg hasx; always @ (noxs) begin if (noxs) begin diff --git a/test_regress/t/t_func_lib_sub.pl b/test_regress/t/t_func_lib_sub.pl index 6c38477cd..8651e4fd4 100755 --- a/test_regress/t/t_func_lib_sub.pl +++ b/test_regress/t/t_func_lib_sub.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt_all => 1); compile( + verilator_flags2 => ['--no-timing'], ); # No execute ok(1); diff --git a/test_regress/t/t_func_lib_sub_timing.pl b/test_regress/t/t_func_lib_sub_timing.pl new file mode 100755 index 000000000..0acb617a6 --- /dev/null +++ b/test_regress/t/t_func_lib_sub_timing.pl @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); # UNOPTTHREADS in vltmt + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_func_lib_sub.v"); + + compile( + verilator_flags2 => ["--timing"], + ); +} +# No execute +ok(1); +1; diff --git a/test_regress/t/t_func_rand.pl b/test_regress/t/t_func_rand.pl index ce5a6dd80..32375a0ef 100755 --- a/test_regress/t/t_func_rand.pl +++ b/test_regress/t/t_func_rand.pl @@ -13,7 +13,7 @@ scenarios(vlt_all => 1); compile( make_top_shell => 0, make_main => 0, - verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp"], + verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp", "--no-timing"], ); execute( diff --git a/test_regress/t/t_fuzz_always_bad.out b/test_regress/t/t_fuzz_always_bad.out index 63a43015a..e208e58ba 100644 --- a/test_regress/t/t_fuzz_always_bad.out +++ b/test_regress/t/t_fuzz_always_bad.out @@ -4,8 +4,4 @@ %Error: t/t_fuzz_always_bad.v:10:19: Can't find definition of task/function: 'h' 10 | always @ c.a c:h; | ^ -%Error-UNSUPPORTED: t/t_fuzz_always_bad.v:10:14: Unsupported: Complex statement in sensitivity list - 10 | always @ c.a c:h; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_gate_basic.pl b/test_regress/t/t_gate_basic.pl index a17622844..b56aa9309 100755 --- a/test_regress/t/t_gate_basic.pl +++ b/test_regress/t/t_gate_basic.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ["--no-timing"], ); execute( diff --git a/test_regress/t/t_gate_basic_timing.pl b/test_regress/t/t_gate_basic_timing.pl new file mode 100755 index 000000000..713929427 --- /dev/null +++ b/test_regress/t/t_gate_basic_timing.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 10e-7 / 10e-9; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_gate_basic.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timing --timescale 10ns/1ns -Wno-RISEFALLDLY"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_gate_chained.pl b/test_regress/t/t_gate_chained.pl index ac9dd39d1..ebaff169c 100755 --- a/test_regress/t/t_gate_chained.pl +++ b/test_regress/t/t_gate_chained.pl @@ -46,7 +46,7 @@ gen($Self->{top_filename}); compile( verilator_flags2 => ["--stats --x-assign fast --x-initial fast", - "-Wno-UNOPTTHREADS"], + "-Wno-UNOPTTHREADS -fno-dfg"], ); execute( diff --git a/test_regress/t/t_gate_delay_unsup.out b/test_regress/t/t_gate_delay_unsup.out index 7a1697074..c3e3e70fd 100644 --- a/test_regress/t/t_gate_delay_unsup.out +++ b/test_regress/t/t_gate_delay_unsup.out @@ -1,12 +1,6 @@ -%Warning-ASSIGNDLY: t/t_gate_basic.v:23:12: Unsupported: Ignoring delay on this primitive. - 23 | not #(0.108) NT0 (nt0, a[0]); - | ^~~~~ - ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_gate_basic.v:24:11: Unsupported: Ignoring delay on this primitive. - 24 | and #1 AN0 (an0, a[0], b[0]); - | ^ -%Warning-ASSIGNDLY: t/t_gate_basic.v:25:12: Unsupported: Ignoring delay on this primitive. +%Warning-RISEFALLDLY: t/t_gate_basic.v:25:12: Unsupported: rising/falling/turn-off delays. Using the first delay 25 | nand #(2,3) ND0 (nd0, a[0], b[0], b[1]); | ^ + ... For warning description see https://verilator.org/warn/RISEFALLDLY?v=latest + ... Use "/* verilator lint_off RISEFALLDLY */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_gate_delay_unsup.pl b/test_regress/t/t_gate_delay_unsup.pl index 2f885e5ae..ecb0ab324 100755 --- a/test_regress/t/t_gate_delay_unsup.pl +++ b/test_regress/t/t_gate_delay_unsup.pl @@ -13,7 +13,7 @@ scenarios(linter => 1); top_filename("t/t_gate_basic.v"); lint( - verilator_flags2 => ["--lint-only -Wall -Wno-DECLFILENAME -Wno-UNUSED"], + verilator_flags2 => ["--lint-only -Wall -Wno-DECLFILENAME -Wno-UNUSED --timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_gate_ormux.pl b/test_regress/t/t_gate_ormux.pl index 02476bffd..3f5118673 100755 --- a/test_regress/t/t_gate_ormux.pl +++ b/test_regress/t/t_gate_ormux.pl @@ -15,7 +15,7 @@ $Self->{sim_time} = $Self->{cycles} * 10 + 1000; compile( v_flags2 => ["+define+SIM_CYCLES=$Self->{cycles}",], - verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats"], + verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats", "-fno-dfg"], ); if ($Self->{vlt}) { diff --git a/test_regress/t/t_gen_forif.pl b/test_regress/t/t_gen_forif.pl index bc8663a80..4017016a5 100755 --- a/test_regress/t/t_gen_forif.pl +++ b/test_regress/t/t_gen_forif.pl @@ -12,6 +12,7 @@ scenarios(simulator => 1); compile( nc_flags2 => ['+access+r'], + verilator_flags2 => ["--no-timing"], ); execute( diff --git a/test_regress/t/t_gen_if.v b/test_regress/t/t_gen_if.v index 3a3f8dc0c..acbd2eb12 100644 --- a/test_regress/t/t_gen_if.v +++ b/test_regress/t/t_gen_if.v @@ -14,6 +14,12 @@ module t(data_i, data_o, single); output [31:0] data_o; input single; + // Bare begin/end extension of IEEE allowed by most all tools + begin + end + begin : named + end : named + //simplistic example, should choose 1st conditional generate and assign straight through //the tool also compiles the special case and determines an error (replication value is 0 generate diff --git a/test_regress/t/t_gen_intdot.pl b/test_regress/t/t_gen_intdot.pl index b46d46042..e245a66a7 100755 --- a/test_regress/t/t_gen_intdot.pl +++ b/test_regress/t/t_gen_intdot.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ["--no-timing"], ); execute( diff --git a/test_regress/t/t_gen_nonconst_bad.out b/test_regress/t/t_gen_nonconst_bad.out new file mode 100644 index 000000000..9fb9ebcb3 --- /dev/null +++ b/test_regress/t/t_gen_nonconst_bad.out @@ -0,0 +1,17 @@ +%Error: t/t_gen_nonconst_bad.v:8:8: Expecting expression to be constant, but can't convert a TESTPLUSARGS to constant. + : ... In instance t + 8 | if ($test$plusargs("BAD-non-constant")) begin + | ^~~~~~~~~~~~~~ +%Error: t/t_gen_nonconst_bad.v:8:8: Generate If condition must evaluate to constant + : ... In instance t + 8 | if ($test$plusargs("BAD-non-constant")) begin + | ^~~~~~~~~~~~~~ +%Error: t/t_gen_nonconst_bad.v:12:7: Expecting expression to be constant, but can't convert a TESTPLUSARGS to constant. + : ... In instance t + 12 | $test$plusargs("BAD-non-constant"): initial $stop; + | ^~~~~~~~~~~~~~ +%Error: t/t_gen_nonconst_bad.v:12:41: Generate Case item does not evaluate to constant + : ... In instance t + 12 | $test$plusargs("BAD-non-constant"): initial $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_gen_nonconst_bad.pl b/test_regress/t/t_gen_nonconst_bad.pl new file mode 100755 index 000000000..a5846c699 --- /dev/null +++ b/test_regress/t/t_gen_nonconst_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_gen_nonconst_bad.v b/test_regress/t/t_gen_nonconst_bad.v new file mode 100644 index 000000000..d8714fb72 --- /dev/null +++ b/test_regress/t/t_gen_nonconst_bad.v @@ -0,0 +1,15 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2012 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + if ($test$plusargs("BAD-non-constant")) begin + initial $stop; + end + case (1) + $test$plusargs("BAD-non-constant"): initial $stop; + endcase + +endmodule diff --git a/test_regress/t/t_gen_upscope.out b/test_regress/t/t_gen_upscope.out index d20955fe2..3765ed4ca 100644 --- a/test_regress/t/t_gen_upscope.out +++ b/test_regress/t/t_gen_upscope.out @@ -1,6 +1,6 @@ +created tag with scope = top.t.tag created tag with scope = top.t.b.gen[0].tag created tag with scope = top.t.b.gen[1].tag -created tag with scope = top.t.tag mod a has scope = top.t mod a has tag = top.t.tag mod b has scope = top.t.b diff --git a/test_regress/t/t_hier_block.v b/test_regress/t/t_hier_block.v index 7c8ee9e15..e7c67c615 100644 --- a/test_regress/t/t_hier_block.v +++ b/test_regress/t/t_hier_block.v @@ -34,13 +34,13 @@ module t (/*AUTOARG*/ `ifdef PROTLIB_TOP secret i_secred(.clk(clk)); `else + /* verilator lint_off UNOPTFLAT */ wire [7:0] out0; wire [7:0] out1; wire [7:0] out2; - /* verilator lint_off UNOPTFLAT */ wire [7:0] out3; - wire [7:0] out3_2; /* verilator lint_on UNOPTFLAT */ + wire [7:0] out3_2; wire [7:0] out5; wire [7:0] out6; int count = 0; @@ -115,7 +115,7 @@ endmodule module sub1( input wire clk, - input wire [7:0] in, + input wire [11:4] in, // Uses higher LSB to cover bug3539 output wire [7:0] out); `HIER_BLOCK logic [7:0] ff; @@ -131,7 +131,7 @@ module sub2( logic [7:0] ff; - // dpi_import_func returns (dpi_eport_func(v) -1) + // dpi_import_func returns (dpi_eport_func(v) -1) import "DPI-C" context function int dpi_import_func(int v); export "DPI-C" function dpi_export_func; diff --git a/test_regress/t/t_hier_block_sc_trace_fst.out b/test_regress/t/t_hier_block_sc_trace_fst.out index 0594e8107..325da4741 100644 --- a/test_regress/t/t_hier_block_sc_trace_fst.out +++ b/test_regress/t/t_hier_block_sc_trace_fst.out @@ -1,5 +1,5 @@ $date - Tue Feb 22 23:52:17 2022 + Tue Oct 18 17:13:03 2022 $end $version @@ -41,7 +41,7 @@ $upscope $end $upscope $end $scope module i_sub1 $end $var wire 1 ! clk $end -$var wire 8 " in [7:0] $end +$var wire 8 " in [11:4] $end $var wire 8 # out [7:0] $end $upscope $end $scope module i_sub2 $end @@ -128,11 +128,11 @@ $upscope $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $scope module sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $var logic 8 A ff [7:0] $end $upscope $end diff --git a/test_regress/t/t_hier_block_sc_trace_vcd.out b/test_regress/t/t_hier_block_sc_trace_vcd.out index 6e0c6affa..cde885b1e 100644 --- a/test_regress/t/t_hier_block_sc_trace_vcd.out +++ b/test_regress/t/t_hier_block_sc_trace_vcd.out @@ -1,57 +1,57 @@ $version Generated by VerilatedVcd $end -$date Sun Dec 19 12:27:52 2021 $end +$date Tue Oct 18 17:12:31 2022 $end $timescale 1ps $end $scope module top $end $scope module t $end - $var wire 1 # clk $end + $var wire 1 * clk $end $var wire 32 + count [31:0] $end - $var wire 8 $ out0 [7:0] $end - $var wire 8 % out1 [7:0] $end - $var wire 8 & out2 [7:0] $end - $var wire 8 ' out3 [7:0] $end - $var wire 8 * out3_2 [7:0] $end + $var wire 8 # out0 [7:0] $end + $var wire 8 $ out1 [7:0] $end + $var wire 8 % out2 [7:0] $end + $var wire 8 & out3 [7:0] $end + $var wire 8 ' out3_2 [7:0] $end $var wire 8 ( out5 [7:0] $end $var wire 8 ) out6 [7:0] $end $scope module i_delay0 $end - $var wire 1 # clk $end - $var wire 8 ' in [7:0] $end + $var wire 1 * clk $end + $var wire 8 & in [7:0] $end $var wire 8 ( out [7:0] $end $upscope $end $scope module i_delay1 $end - $var wire 1 # clk $end + $var wire 1 * clk $end $var wire 8 ( in [7:0] $end $var wire 8 ) out [7:0] $end $upscope $end $scope module i_sub0 $end - $var wire 1 # clk $end - $var wire 8 ' in [7:0] $end - $var wire 8 $ out [7:0] $end + $var wire 1 * clk $end + $var wire 8 & in [7:0] $end + $var wire 8 # out [7:0] $end $scope module i_sub0 $end - $var wire 1 # clk $end - $var wire 8 ' in [7:0] $end - $var wire 8 $ out [7:0] $end + $var wire 1 * clk $end + $var wire 8 & in [7:0] $end + $var wire 8 # out [7:0] $end $upscope $end $upscope $end $scope module i_sub1 $end - $var wire 1 # clk $end + $var wire 1 * clk $end + $var wire 8 # in [11:4] $end + $var wire 8 $ out [7:0] $end + $upscope $end + $scope module i_sub2 $end + $var wire 1 * clk $end $var wire 8 $ in [7:0] $end $var wire 8 % out [7:0] $end $upscope $end - $scope module i_sub2 $end - $var wire 1 # clk $end + $scope module i_sub3 $end + $var wire 1 * clk $end $var wire 8 % in [7:0] $end $var wire 8 & out [7:0] $end $upscope $end - $scope module i_sub3 $end - $var wire 1 # clk $end - $var wire 8 & in [7:0] $end - $var wire 8 ' out [7:0] $end - $upscope $end $scope module i_sub3_2 $end - $var wire 1 # clk $end - $var wire 8 & in [7:0] $end - $var wire 8 * out [7:0] $end + $var wire 1 * clk $end + $var wire 8 % in [7:0] $end + $var wire 8 ' out [7:0] $end $upscope $end $upscope $end $upscope $end @@ -122,12 +122,12 @@ $timescale 1ps $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 D clk $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $scope module sub1 $end $var wire 1 D clk $end $var wire 8 G ff [7:0] $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $upscope $end $upscope $end @@ -1016,14 +1016,14 @@ $enddefinitions $end #0 -0# +b00000000 # b00000000 $ b00000000 % b00000000 & b00000000 ' b00000000 ( b00000000 ) -b00000000 * +0* b00000000000000000000000000000000 + 0- b00000000 . @@ -1502,11 +1502,11 @@ b00000001 ') b00000011 () b00000000000000000000000000000001 )) #10 -1# -b00000001 % +b00000001 $ +b00000010 % b00000010 & b00000010 ' -b00000010 * +1* b00000000000000000000000000000001 + 1- b00000010 . @@ -1677,7 +1677,7 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 #13 #14 #15 -0# +0* 0- 08 0? @@ -1703,11 +1703,11 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 #18 #19 #20 -1# -b00000010 $ -b00000101 & +b00000010 # +b00000101 % +b00000011 & b00000011 ' -b00000011 * +1* b00000000000000000000000000000010 + 1- b00000011 . @@ -2017,7 +2017,7 @@ b00000001 #) #23 #24 #25 -0# +0* 0- 08 0? @@ -2043,13 +2043,13 @@ b00000001 #) #28 #29 #30 -1# +b00000011 # b00000011 $ -b00000011 % -b00000111 & +b00000111 % +b00000101 & b00000101 ' b00000010 ( -b00000101 * +1* b00000000000000000000000000000011 + 1- b00000101 . @@ -2345,7 +2345,7 @@ b00000000 #) #33 #34 #35 -0# +0* 0- 08 0? @@ -2371,13 +2371,13 @@ b00000000 #) #38 #39 #40 -1# -b00000101 $ -b00000100 % +b00000101 # +b00000100 $ +b00001000 % b00001000 & b00001000 ' b00000011 ( -b00001000 * +1* b00000000000000000000000000000100 + 1- b00001000 . @@ -2673,7 +2673,7 @@ b00000001 #) #43 #44 #45 -0# +0* 0- 08 0? @@ -2699,12 +2699,12 @@ b00000001 #) #48 #49 #50 -1# -b00001000 $ -b00000110 % +b00001000 # +b00000110 $ +b00001010 & b00001010 ' b00000101 ( -b00001010 * +1* b00000000000000000000000000000101 + 1- b00001010 . @@ -2992,7 +2992,7 @@ b00000000 #) #53 #54 #55 -0# +0* 0- 08 0? @@ -3018,14 +3018,14 @@ b00000000 #) #58 #59 #60 -1# -b00001010 $ -b00001001 % -b00001010 & +b00001010 # +b00001001 $ +b00001010 % +b00001011 & b00001011 ' b00001000 ( b00000010 ) -b00001011 * +1* b00000000000000000000000000000110 + 1- b00001011 . @@ -3321,7 +3321,7 @@ b00000001 #) #63 #64 #65 -0# +0* 0- 08 0? @@ -3347,12 +3347,12 @@ b00000001 #) #68 #69 #70 -1# +b00001011 # b00001011 $ b00001011 % -b00001011 & b00001010 ( b00000011 ) +1* b00000000000000000000000000000111 + 1- b00001010 / @@ -3638,7 +3638,7 @@ b00000000 #) #73 #74 #75 -0# +0* 0- 08 0? @@ -3664,13 +3664,13 @@ b00000000 #) #78 #79 #80 -1# -b00001100 % +b00001100 $ +b00001101 % b00001101 & b00001101 ' b00001011 ( b00000101 ) -b00001101 * +1* b00000000000000000000000000001000 + 1- b00001101 . @@ -3968,7 +3968,7 @@ b00000001 #) #83 #84 #85 -0# +0* 0- 08 0? @@ -3994,12 +3994,12 @@ b00000001 #) #88 #89 #90 -1# -b00001101 $ -b00010000 & +b00001101 # +b00010000 % +b00001110 & b00001110 ' b00001000 ) -b00001110 * +1* b00000000000000000000000000001001 + 1- b00001110 . @@ -4295,7 +4295,7 @@ b00000000 #) #93 #94 #95 -0# +0* 0- 08 0? @@ -4321,14 +4321,14 @@ b00000000 #) #98 #99 #100 -1# +b00001110 # b00001110 $ -b00001110 % -b00010010 & +b00010010 % +b00010000 & b00010000 ' b00001101 ( b00001010 ) -b00010000 * +1* b00000000000000000000000000001010 + 1- b00010000 . @@ -4627,7 +4627,7 @@ b00000001 #) #103 #104 #105 -0# +0* 0- 08 0? @@ -4653,14 +4653,14 @@ b00000001 #) #108 #109 #110 -1# -b00010000 $ -b00001111 % +b00010000 # +b00001111 $ +b00010011 % b00010011 & b00010011 ' b00001110 ( b00001011 ) -b00010011 * +1* b00000000000000000000000000001011 + 1- b00010011 . @@ -4958,7 +4958,7 @@ b00000000 #) #113 #114 #115 -0# +0* 0- 08 0? @@ -4984,12 +4984,12 @@ b00000000 #) #118 #119 #120 -1# -b00010011 $ -b00010001 % +b00010011 # +b00010001 $ +b00010101 & b00010101 ' b00010000 ( -b00010101 * +1* b00000000000000000000000000001100 + 1- b00010101 . @@ -5277,7 +5277,7 @@ b00000001 #) #123 #124 #125 -0# +0* 0- 08 0? @@ -5303,14 +5303,14 @@ b00000001 #) #128 #129 #130 -1# -b00010101 $ -b00010100 % -b00010101 & +b00010101 # +b00010100 $ +b00010101 % +b00010110 & b00010110 ' b00010011 ( b00001101 ) -b00010110 * +1* b00000000000000000000000000001101 + 1- b00010110 . @@ -5606,7 +5606,7 @@ b00000000 #) #133 #134 #135 -0# +0* 0- 08 0? @@ -5632,12 +5632,12 @@ b00000000 #) #138 #139 #140 -1# +b00010110 # b00010110 $ b00010110 % -b00010110 & b00010101 ( b00001110 ) +1* b00000000000000000000000000001110 + 1- b00010101 / @@ -5923,7 +5923,7 @@ b00000001 #) #143 #144 #145 -0# +0* 0- 08 0? @@ -5949,13 +5949,13 @@ b00000001 #) #148 #149 #150 -1# -b00010111 % +b00010111 $ +b00011000 % b00011000 & b00011000 ' b00010110 ( b00010000 ) -b00011000 * +1* b00000000000000000000000000001111 + 1- b00011000 . @@ -6253,7 +6253,7 @@ b00000000 #) #153 #154 #155 -0# +0* 0- 08 0? @@ -6279,12 +6279,12 @@ b00000000 #) #158 #159 #160 -1# -b00011000 $ -b00011011 & +b00011000 # +b00011011 % +b00011001 & b00011001 ' b00010011 ) -b00011001 * +1* b00000000000000000000000000010000 + 1- b00011001 . @@ -6580,7 +6580,7 @@ b00000001 #) #163 #164 #165 -0# +0* 0- 08 0? @@ -6606,14 +6606,14 @@ b00000001 #) #168 #169 #170 -1# +b00011001 # b00011001 $ -b00011001 % -b00011101 & +b00011101 % +b00011011 & b00011011 ' b00011000 ( b00010101 ) -b00011011 * +1* b00000000000000000000000000010001 + 1- b00011011 . diff --git a/test_regress/t/t_hier_block_trace_fst.out b/test_regress/t/t_hier_block_trace_fst.out index 366eef0b4..1ae808652 100644 --- a/test_regress/t/t_hier_block_trace_fst.out +++ b/test_regress/t/t_hier_block_trace_fst.out @@ -1,5 +1,5 @@ $date - Tue Feb 22 23:53:34 2022 + Tue Oct 18 17:19:55 2022 $end $version @@ -42,7 +42,7 @@ $upscope $end $upscope $end $scope module i_sub1 $end $var wire 1 ! clk $end -$var wire 8 " in [7:0] $end +$var wire 8 " in [11:4] $end $var wire 8 # out [7:0] $end $upscope $end $scope module i_sub2 $end @@ -129,11 +129,11 @@ $upscope $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $scope module sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $var logic 8 A ff [7:0] $end $upscope $end diff --git a/test_regress/t/t_hier_block_trace_vcd.out b/test_regress/t/t_hier_block_trace_vcd.out index cd600aaa9..993b40bbe 100644 --- a/test_regress/t/t_hier_block_trace_vcd.out +++ b/test_regress/t/t_hier_block_trace_vcd.out @@ -1,58 +1,58 @@ $version Generated by VerilatedVcd $end -$date Sat Nov 27 16:51:43 2021 $end +$date Tue Oct 18 17:20:50 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $scope module t $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 32 + count [31:0] $end $var wire 8 # out0 [7:0] $end $var wire 8 $ out1 [7:0] $end $var wire 8 % out2 [7:0] $end $var wire 8 & out3 [7:0] $end - $var wire 8 * out3_2 [7:0] $end - $var wire 8 ' out5 [7:0] $end - $var wire 8 ( out6 [7:0] $end + $var wire 8 ' out3_2 [7:0] $end + $var wire 8 ( out5 [7:0] $end + $var wire 8 ) out6 [7:0] $end $scope module i_delay0 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 & in [7:0] $end - $var wire 8 ' out [7:0] $end - $upscope $end - $scope module i_delay1 $end - $var wire 1 ) clk $end - $var wire 8 ' in [7:0] $end $var wire 8 ( out [7:0] $end $upscope $end + $scope module i_delay1 $end + $var wire 1 * clk $end + $var wire 8 ( in [7:0] $end + $var wire 8 ) out [7:0] $end + $upscope $end $scope module i_sub0 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 & in [7:0] $end $var wire 8 # out [7:0] $end $scope module i_sub0 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 & in [7:0] $end $var wire 8 # out [7:0] $end $upscope $end $upscope $end $scope module i_sub1 $end - $var wire 1 ) clk $end - $var wire 8 # in [7:0] $end + $var wire 1 * clk $end + $var wire 8 # in [11:4] $end $var wire 8 $ out [7:0] $end $upscope $end $scope module i_sub2 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 $ in [7:0] $end $var wire 8 % out [7:0] $end $upscope $end $scope module i_sub3 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 % in [7:0] $end $var wire 8 & out [7:0] $end $upscope $end $scope module i_sub3_2 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 % in [7:0] $end - $var wire 8 * out [7:0] $end + $var wire 8 ' out [7:0] $end $upscope $end $upscope $end $upscope $end @@ -123,12 +123,12 @@ $timescale 1ps $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 D clk $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $scope module sub1 $end $var wire 1 D clk $end $var wire 8 G ff [7:0] $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $upscope $end $upscope $end @@ -1023,8 +1023,8 @@ b00000000 % b00000000 & b00000000 ' b00000000 ( -0) -b00000000 * +b00000000 ) +0* b00000000000000000000000000000000 + 0- b00000000 . @@ -1506,8 +1506,8 @@ b00000000000000000000000000000001 )) b00000001 $ b00000010 % b00000010 & -1) -b00000010 * +b00000010 ' +1* b00000000000000000000000000000001 + 1- b00000010 . @@ -1674,7 +1674,7 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 t( b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101 x( #15 -0) +0* 0- 08 0? @@ -1699,8 +1699,8 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 b00000010 # b00000101 % b00000011 & -1) -b00000011 * +b00000011 ' +1* b00000000000000000000000000000010 + 1- b00000011 . @@ -2006,7 +2006,7 @@ b00000011 !) b00000010 ") b00000001 #) #25 -0) +0* 0- 08 0? @@ -2032,9 +2032,9 @@ b00000011 # b00000011 $ b00000111 % b00000101 & -b00000010 ' -1) -b00000101 * +b00000101 ' +b00000010 ( +1* b00000000000000000000000000000011 + 1- b00000101 . @@ -2326,7 +2326,7 @@ b00000000 !) b00000000 ") b00000000 #) #35 -0) +0* 0- 08 0? @@ -2352,9 +2352,9 @@ b00000101 # b00000100 $ b00001000 % b00001000 & -b00000011 ' -1) -b00001000 * +b00001000 ' +b00000011 ( +1* b00000000000000000000000000000100 + 1- b00001000 . @@ -2646,7 +2646,7 @@ b00000011 !) b00000010 ") b00000001 #) #45 -0) +0* 0- 08 0? @@ -2671,9 +2671,9 @@ b00000001 #) b00001000 # b00000110 $ b00001010 & -b00000101 ' -1) -b00001010 * +b00001010 ' +b00000101 ( +1* b00000000000000000000000000000101 + 1- b00001010 . @@ -2957,7 +2957,7 @@ b00000000 !) b00000000 ") b00000000 #) #55 -0) +0* 0- 08 0? @@ -2983,10 +2983,10 @@ b00001010 # b00001001 $ b00001010 % b00001011 & -b00001000 ' -b00000010 ( -1) -b00001011 * +b00001011 ' +b00001000 ( +b00000010 ) +1* b00000000000000000000000000000110 + 1- b00001011 . @@ -3278,7 +3278,7 @@ b00000011 !) b00000010 ") b00000001 #) #65 -0) +0* 0- 08 0? @@ -3303,9 +3303,9 @@ b00000001 #) b00001011 # b00001011 $ b00001011 % -b00001010 ' -b00000011 ( -1) +b00001010 ( +b00000011 ) +1* b00000000000000000000000000000111 + 1- b00001010 / @@ -3587,7 +3587,7 @@ b00000000 !) b00000000 ") b00000000 #) #75 -0) +0* 0- 08 0? @@ -3612,10 +3612,10 @@ b00000000 #) b00001100 $ b00001101 % b00001101 & -b00001011 ' -b00000101 ( -1) -b00001101 * +b00001101 ' +b00001011 ( +b00000101 ) +1* b00000000000000000000000000001000 + 1- b00001101 . @@ -3909,7 +3909,7 @@ b00000011 !) b00000010 ") b00000001 #) #85 -0) +0* 0- 08 0? @@ -3934,9 +3934,9 @@ b00000001 #) b00001101 # b00010000 % b00001110 & -b00001000 ( -1) -b00001110 * +b00001110 ' +b00001000 ) +1* b00000000000000000000000000001001 + 1- b00001110 . @@ -4228,7 +4228,7 @@ b00000000 !) b00000000 ") b00000000 #) #95 -0) +0* 0- 08 0? @@ -4254,10 +4254,10 @@ b00001110 # b00001110 $ b00010010 % b00010000 & -b00001101 ' -b00001010 ( -1) -b00010000 * +b00010000 ' +b00001101 ( +b00001010 ) +1* b00000000000000000000000000001010 + 1- b00010000 . @@ -4552,7 +4552,7 @@ b00000011 !) b00000010 ") b00000001 #) #105 -0) +0* 0- 08 0? @@ -4578,10 +4578,10 @@ b00010000 # b00001111 $ b00010011 % b00010011 & -b00001110 ' -b00001011 ( -1) -b00010011 * +b00010011 ' +b00001110 ( +b00001011 ) +1* b00000000000000000000000000001011 + 1- b00010011 . @@ -4875,7 +4875,7 @@ b00000000 !) b00000000 ") b00000000 #) #115 -0) +0* 0- 08 0? @@ -4900,9 +4900,9 @@ b00000000 #) b00010011 # b00010001 $ b00010101 & -b00010000 ' -1) -b00010101 * +b00010101 ' +b00010000 ( +1* b00000000000000000000000000001100 + 1- b00010101 . @@ -5186,7 +5186,7 @@ b00000011 !) b00000010 ") b00000001 #) #125 -0) +0* 0- 08 0? @@ -5212,10 +5212,10 @@ b00010101 # b00010100 $ b00010101 % b00010110 & -b00010011 ' -b00001101 ( -1) -b00010110 * +b00010110 ' +b00010011 ( +b00001101 ) +1* b00000000000000000000000000001101 + 1- b00010110 . @@ -5507,7 +5507,7 @@ b00000000 !) b00000000 ") b00000000 #) #135 -0) +0* 0- 08 0? @@ -5532,9 +5532,9 @@ b00000000 #) b00010110 # b00010110 $ b00010110 % -b00010101 ' -b00001110 ( -1) +b00010101 ( +b00001110 ) +1* b00000000000000000000000000001110 + 1- b00010101 / @@ -5816,7 +5816,7 @@ b00000011 !) b00000010 ") b00000001 #) #145 -0) +0* 0- 08 0? @@ -5841,10 +5841,10 @@ b00000001 #) b00010111 $ b00011000 % b00011000 & -b00010110 ' -b00010000 ( -1) -b00011000 * +b00011000 ' +b00010110 ( +b00010000 ) +1* b00000000000000000000000000001111 + 1- b00011000 . @@ -6138,7 +6138,7 @@ b00000000 !) b00000000 ") b00000000 #) #155 -0) +0* 0- 08 0? @@ -6163,9 +6163,9 @@ b00000000 #) b00011000 # b00011011 % b00011001 & -b00010011 ( -1) -b00011001 * +b00011001 ' +b00010011 ) +1* b00000000000000000000000000010000 + 1- b00011001 . @@ -6457,7 +6457,7 @@ b00000011 !) b00000010 ") b00000001 #) #165 -0) +0* 0- 08 0? @@ -6483,10 +6483,10 @@ b00011001 # b00011001 $ b00011101 % b00011011 & -b00011000 ' -b00010101 ( -1) -b00011011 * +b00011011 ' +b00011000 ( +b00010101 ) +1* b00000000000000000000000000010001 + 1- b00011011 . diff --git a/test_regress/t/t_increment_bad.out b/test_regress/t/t_increment_bad.out index ef339225a..f10697bd0 100644 --- a/test_regress/t/t_increment_bad.out +++ b/test_regress/t/t_increment_bad.out @@ -20,4 +20,7 @@ %Error-UNSUPPORTED: t/t_increment_bad.v:23:24: Unsupported: Incrementation in this context. 23 | pos = array[0][0]++; | ^~ +%Error-UNSUPPORTED: t/t_increment_bad.v:26:37: Unsupported: Incrementation in this context. + 26 | assert property (@(posedge clk) a++ >= 0); + | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_increment_bad.v b/test_regress/t/t_increment_bad.v index 7ce03d704..86755af51 100644 --- a/test_regress/t/t_increment_bad.v +++ b/test_regress/t/t_increment_bad.v @@ -22,4 +22,6 @@ module t (/*AUTOARG*/ pos = array[0][0]++; end + + assert property (@(posedge clk) a++ >= 0); endmodule diff --git a/test_regress/t/t_inside2.pl b/test_regress/t/t_inside2.pl new file mode 100755 index 000000000..552bd97db --- /dev/null +++ b/test_regress/t/t_inside2.pl @@ -0,0 +1,17 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +ok(1); +1; diff --git a/test_regress/t/t_inside2.v b/test_regress/t/t_inside2.v new file mode 100644 index 000000000..4cea304f6 --- /dev/null +++ b/test_regress/t/t_inside2.v @@ -0,0 +1,37 @@ +// DESCRIPTION::Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + + input clk; + + typedef struct packed { + logic signed [63:0] b; + } a_t; + + a_t a_r; + a_t a_n; + logic signed [63:0] b; + logic res; + + assign b = a_r.b; + + always_comb begin + a_n = a_r; + res = '0; + if (b inside {1, 2}) begin + res = 1'b1; + end + end + + always_ff @(posedge clk) begin + a_r <= a_n; + end + +endmodule diff --git a/test_regress/t/t_inst_2star_bad.out b/test_regress/t/t_inst_2star_bad.out new file mode 100644 index 000000000..fc7cc9140 --- /dev/null +++ b/test_regress/t/t_inst_2star_bad.out @@ -0,0 +1,7 @@ +%Error: t/t_inst_2star_bad.v:11:17: Duplicate .* in an instance (IEEE 1800-2017 23.3.2) + 11 | sub sub (.*, .*); + | ^~ +%Error: t/t_inst_2star_bad.v:13:13: Connect by position is illegal in .* connected instances (IEEE 1800-2017 23.3.2) + 13 | sub sub (foo, .*); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_order_clkinst_bad.pl b/test_regress/t/t_inst_2star_bad.pl similarity index 82% rename from test_regress/t/t_order_clkinst_bad.pl rename to test_regress/t/t_inst_2star_bad.pl index 165ed807a..35d749208 100755 --- a/test_regress/t/t_order_clkinst_bad.pl +++ b/test_regress/t/t_inst_2star_bad.pl @@ -8,12 +8,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -scenarios(simulator => 1); +scenarios(linter => 1); -top_filename("t/t_order_clkinst.v"); - -compile( - v_flags2 => ["-Wwarn-IMPERFECTSCH"], +lint( fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_inst_2star_bad.v b/test_regress/t/t_inst_2star_bad.v new file mode 100644 index 000000000..5c91aaac5 --- /dev/null +++ b/test_regress/t/t_inst_2star_bad.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + wire foo; + + sub sub (.*, .*); + + sub sub (foo, .*); + +endmodule + +module sub (input foo); +endmodule diff --git a/test_regress/t/t_inst_ccall.v b/test_regress/t/t_inst_ccall.v index 5741ed46c..63c48d8c9 100644 --- a/test_regress/t/t_inst_ccall.v +++ b/test_regress/t/t_inst_ccall.v @@ -12,7 +12,6 @@ module t (/*AUTOARG*/ input clk; integer cyc; initial cyc=1; - // verilator lint_on GENCLK reg [31:0] long; reg [63:0] quad; wire [31:0] longout; diff --git a/test_regress/t/t_inst_tree.v b/test_regress/t/t_inst_tree.v index 3533d8e76..092ff2c17 100644 --- a/test_regress/t/t_inst_tree.v +++ b/test_regress/t/t_inst_tree.v @@ -12,9 +12,9 @@ module t (/*AUTOARG*/ input clk; integer cyc; initial cyc=1; - // verilator lint_off GENCLK + reg printclk; - // verilator lint_on GENCLK + ps ps (printclk); reg [7:0] a; diff --git a/test_regress/t/t_inst_tree_inl0_pub1.pl b/test_regress/t/t_inst_tree_inl0_pub1.pl index bad6139b0..107b1e56d 100755 --- a/test_regress/t/t_inst_tree_inl0_pub1.pl +++ b/test_regress/t/t_inst_tree_inl0_pub1.pl @@ -42,7 +42,7 @@ if ($Self->{vlt_all}) { # We expect to combine sequent functions across multiple instances of # l2, l3, l4, l5. If this number drops, please confirm this has not broken. file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, - ($Self->{vltmt} ? 84 : 52)); + ($Self->{vltmt} ? 85 : 67)); # Everything should use relative references checkRelativeRefs("t", 1); diff --git a/test_regress/t/t_inst_tree_inl1_pub0.pl b/test_regress/t/t_inst_tree_inl1_pub0.pl index 9e8e50970..b8f738b3e 100755 --- a/test_regress/t/t_inst_tree_inl1_pub0.pl +++ b/test_regress/t/t_inst_tree_inl1_pub0.pl @@ -14,7 +14,7 @@ top_filename("t/t_inst_tree.v"); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - v_flags2 => ["$Self->{t_dir}/t_inst_tree_inl1_pub0.vlt"], + v_flags2 => ["-fno-dfg-post-inline", "$Self->{t_dir}/t_inst_tree_inl1_pub0.vlt"], ); if ($Self->{vlt_all}) { diff --git a/test_regress/t/t_inst_tree_inl1_pub1.pl b/test_regress/t/t_inst_tree_inl1_pub1.pl index 2c8ff9ac9..4240d8339 100755 --- a/test_regress/t/t_inst_tree_inl1_pub1.pl +++ b/test_regress/t/t_inst_tree_inl1_pub1.pl @@ -14,7 +14,7 @@ top_filename("t/t_inst_tree.v"); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - v_flags2 => ["t/$Self->{name}.vlt", + v_flags2 => ["-fno-dfg-post-inline", "t/$Self->{name}.vlt", $Self->wno_unopthreads_for_few_cores()] ); diff --git a/test_regress/t/t_interface_top_bad.pl b/test_regress/t/t_interface_top_bad.pl index 07964a1b5..8eda3a219 100755 --- a/test_regress/t/t_interface_top_bad.pl +++ b/test_regress/t/t_interface_top_bad.pl @@ -10,7 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -lint( +compile( fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_interface_virtual.out b/test_regress/t/t_interface_virtual.out new file mode 100644 index 000000000..2e63964b4 --- /dev/null +++ b/test_regress/t/t_interface_virtual.out @@ -0,0 +1,7 @@ +va.addr=aa va.data=11 ia.addr=aa ia.data=11 +vb.addr=bb vb.data=22 ib.addr=bb ib.data=22 +ca.fa.addr=a0 ca.fa.data=11 ca.fa.addr=b0 ca.fb.data=22 +cb.fa.addr=b0 cb.fa.data=22 cb.fa.addr=a0 cb.fb.data=11 +gen.x[0].addr=a0 gen.x[1].addr=b0 +gen='{x:'{top.t.ia, top.t.ib, null, null} } +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual.pl b/test_regress/t/t_interface_virtual.pl new file mode 100755 index 000000000..6247bd126 --- /dev/null +++ b/test_regress/t/t_interface_virtual.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual.v b/test_regress/t/t_interface_virtual.v new file mode 100644 index 000000000..878df0995 --- /dev/null +++ b/test_regress/t/t_interface_virtual.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_interface_virtual_bad.v + +interface PBus; + logic req, grant; + logic [7:0] addr, data; + modport phy(input addr, ref data); +endinterface + +typedef virtual PBus vpbus_t; +typedef vpbus_t vpbus2_t; + +class Cls; + vpbus2_t fa, fb; +endclass + +class Clsgen#(type T = logic); + T x[0:3]; +endclass + +module t (/*AUTOARG*/); + + PBus ia, ib; + virtual PBus va, vb; + virtual PBus.phy pa, pb; + Cls ca, cb; + Clsgen#(virtual PBus) gen; + + initial begin + va = ia; + vb = ib; + + ca = new; + cb = new; + gen = new; + + va.addr = 8'haa; + ia.data = 8'h11; + + vb.addr = 8'hbb; + ib.data = 8'h22; + + $display("va.addr=%x", va.addr, " va.data=%x", va.data, " ia.addr=%x", ia.addr, " ia.data=%x", ia.data); + $display("vb.addr=%x", vb.addr, " vb.data=%x", vb.data, " ib.addr=%x", ib.addr, " ib.data=%x", ib.data); + + ca.fa = ia; + ca.fb = ib; + cb.fa = ib; + cb.fb = ia; + gen.x[0] = va; + gen.x[1] = vb; + + pa = va; + pb = vb; + + pb.addr = 8'hb0; + pa.addr = 8'ha0; + + $display("ca.fa.addr=%x", ca.fa.addr, " ca.fa.data=%x", ca.fa.data, " ca.fa.addr=%x", ca.fb.addr, " ca.fb.data=%x", ca.fb.data); + $display("cb.fa.addr=%x", cb.fa.addr, " cb.fa.data=%x", cb.fa.data, " cb.fa.addr=%x", cb.fb.addr, " cb.fb.data=%x", cb.fb.data); + $display("gen.x[0].addr=%x", gen.x[0].addr, " gen.x[1].addr=%x", gen.x[1].addr); + $display("gen=%p", gen); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_virtual_bad.out b/test_regress/t/t_interface_virtual_bad.out new file mode 100644 index 000000000..6afbdc125 --- /dev/null +++ b/test_regress/t/t_interface_virtual_bad.out @@ -0,0 +1,34 @@ +%Error: t/t_interface_virtual_bad.v:31:12: Operator ASSIGN expected 'PBus' interface on Assign RHS but 'q8__Viftop' is a different interface ('QBus'). + : ... In instance t + 31 | v8 = q8; + | ^~ +%Error: t/t_interface_virtual_bad.v:35:12: Operator ASSIGN expected no interface modport on Assign RHS but got 'phy' modport. + : ... In instance t + 35 | v8 = v8_phy; + | ^~~~~~ +%Error: t/t_interface_virtual_bad.v:37:17: Operator ASSIGN expected non-interface on Assign RHS but 'p8__Viftop' is an interface. + : ... In instance t + 37 | data = p8.phy; + | ^~~ +%Error: t/t_interface_virtual_bad.v:38:14: Operator ASSIGN expected non-interface on Assign RHS but 'v8_phy' is an interface. + : ... In instance t + 38 | data = v8_phy; + | ^~~~~~ +%Error: t/t_interface_virtual_bad.v:39:14: Operator ASSIGN expected non-interface on Assign RHS but 'v8' is an interface. + : ... In instance t + 39 | data = v8; + | ^~ +%Error: t/t_interface_virtual_bad.v:40:14: Operator ASSIGN expected non-interface on Assign RHS but 'p8__Viftop' is an interface. + : ... In instance t + 40 | data = p8; + | ^~ +%Error: t/t_interface_virtual_bad.v:41:12: Operator ASSIGN expected 'PBus' interface on Assign RHS but 'data' is not an interface. + : ... In instance t + 41 | v8 = data; + | ^~~~ +%Error: t/t_interface_virtual_bad.v:44:79: Member 'gran' not found in interface 'PBus' + : ... In instance t + : ... Suggested alternative: 'grant' + 44 | $display("q8.grant=", p8.grant, " v8.grant=", v8.grant, v8_phy.addr, v8.gran); + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_interface_virtual_bad.pl b/test_regress/t/t_interface_virtual_bad.pl new file mode 100755 index 000000000..7be596e0f --- /dev/null +++ b/test_regress/t/t_interface_virtual_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_bad.v b/test_regress/t/t_interface_virtual_bad.v new file mode 100644 index 000000000..3c4f690da --- /dev/null +++ b/test_regress/t/t_interface_virtual_bad.v @@ -0,0 +1,49 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_interface_virtual.v + +interface PBus; + logic req, grant; + logic [7:0] addr, data; + modport phy(input addr, ref data); +endinterface + +interface QBus; +endinterface + +typedef virtual PBus vpbus_t; + +module t (/*AUTOARG*/); + + PBus p8; + QBus q8; + vpbus_t v8; + virtual PBus.phy v8_phy; + logic data; + + initial begin + v8 = p8; + p8 = v8; // error + v8 = q8; // error + v8_phy = p8; + v8_phy = v8; + v8_phy = p8.phy; + v8 = v8_phy; // error + v8 = p8.phy; // error + data = p8.phy; // error + data = v8_phy; // error + data = v8; // error + data = p8; // error + v8 = data; // error + v8.grant = 1'b1; + + $display("q8.grant=", p8.grant, " v8.grant=", v8.grant, v8_phy.addr, v8.gran); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_virtual_inl.pl b/test_regress/t/t_interface_virtual_inl.pl new file mode 100755 index 000000000..b342b7b3a --- /dev/null +++ b/test_regress/t/t_interface_virtual_inl.pl @@ -0,0 +1,27 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_interface_virtual.v"); +golden_filename("t/t_interface_virtual.out"); + +compile( + # Avoid inlining so we find bugs in the non-inliner connection code + verilator_flags2 => ["-fno-inline"], + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_unused_bad.out b/test_regress/t/t_interface_virtual_unused_bad.out new file mode 100644 index 000000000..5775cfc79 --- /dev/null +++ b/test_regress/t/t_interface_virtual_unused_bad.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_interface_virtual_unused_bad.v:14:12: Unsupported: virtual interface never assigned any actual interface + 14 | virtual QBus q8; + | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_interface_virtual_unused_bad.pl b/test_regress/t/t_interface_virtual_unused_bad.pl new file mode 100755 index 000000000..7be596e0f --- /dev/null +++ b/test_regress/t/t_interface_virtual_unused_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_unused_bad.v b/test_regress/t/t_interface_virtual_unused_bad.v new file mode 100644 index 000000000..c0c7d185f --- /dev/null +++ b/test_regress/t/t_interface_virtual_unused_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_interface_virtual.v + +interface QBus; +endinterface + +module t (/*AUTOARG*/); + + virtual QBus q8; + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lib.pl b/test_regress/t/t_lib.pl index 93aa4bfd8..ef6fcd3b2 100755 --- a/test_regress/t/t_lib.pl +++ b/test_regress/t/t_lib.pl @@ -30,6 +30,7 @@ while (1) { run(logfile => "$secret_dir/vlt_compile.log", cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -52,6 +53,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-LDFLAGS", "$secret_prefix/libsecret.a"], xsim_flags2 => ["$secret_dir/secret.sv"], diff --git a/test_regress/t/t_lib_nolib.pl b/test_regress/t/t_lib_nolib.pl index d4b9b47e9..0a385f0cb 100755 --- a/test_regress/t/t_lib_nolib.pl +++ b/test_regress/t/t_lib_nolib.pl @@ -21,7 +21,7 @@ top_filename("t/t_lib_prot.v"); # Tests the same code as t_lib_prot.pl but without --protect-lib compile( - verilator_flags2 => ["t/t_lib_prot_secret.v"], + verilator_flags2 => ["t/t_lib_prot_secret.v", '--no-timing'], xsim_flags2 => ["t/t_lib_prot_secret.v"], ); diff --git a/test_regress/t/t_lib_prot.pl b/test_regress/t/t_lib_prot.pl index 2df0816e8..14c01dc7c 100755 --- a/test_regress/t/t_lib_prot.pl +++ b/test_regress/t/t_lib_prot.pl @@ -28,6 +28,7 @@ while (1) { run(logfile => "$secret_dir/vlt_compile.log", cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -52,6 +53,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-LDFLAGS", "$secret_prefix/libsecret.a"], xsim_flags2 => ["$secret_dir/secret.sv"], diff --git a/test_regress/t/t_lib_prot.v b/test_regress/t/t_lib_prot.v index 7da04b3ed..7fa639835 100644 --- a/test_regress/t/t_lib_prot.v +++ b/test_regress/t/t_lib_prot.v @@ -153,7 +153,7 @@ module t #(parameter GATED_CLK = 0) (/*AUTOARG*/ logic possibly_gated_clk; if (GATED_CLK != 0) begin: yes_gated_clock - logic clk_en_latch /*verilator clock_enable*/; + logic clk_en_latch; /* verilator lint_off COMBDLY */ /* verilator lint_off LATCH */ always_comb if (clk == '0) clk_en_latch <= clk_en; diff --git a/test_regress/t/t_lib_prot_clk_gated.pl b/test_regress/t/t_lib_prot_clk_gated.pl index 4a290d26d..b1286cca2 100755 --- a/test_regress/t/t_lib_prot_clk_gated.pl +++ b/test_regress/t/t_lib_prot_clk_gated.pl @@ -29,6 +29,7 @@ while (1) { run(logfile => "$secret_dir/vlt_compile.log", cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -54,6 +55,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-GGATED_CLK=1", "-LDFLAGS", "$secret_prefix/libsecret.a"], diff --git a/test_regress/t/t_lib_prot_delay_bad.out b/test_regress/t/t_lib_prot_delay_bad.out new file mode 100644 index 000000000..37f5d76b7 --- /dev/null +++ b/test_regress/t/t_lib_prot_delay_bad.out @@ -0,0 +1,3 @@ +%Error-UNSUPPORTED: Unsupported: --lib-create with --timing and delays + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_lib_prot_delay_bad.pl b/test_regress/t/t_lib_prot_delay_bad.pl new file mode 100755 index 000000000..7bb3853cf --- /dev/null +++ b/test_regress/t/t_lib_prot_delay_bad.pl @@ -0,0 +1,26 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2019 by Todd Strader. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile ( + verilator_flags2 => ["--protect-lib", + "secret", + "--protect-key", + "secret-key", + "--timing", + ], + verilator_make_gcc => 0, + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lib_prot_delay_bad.v b/test_regress/t/t_lib_prot_delay_bad.v new file mode 100644 index 000000000..832e60f2e --- /dev/null +++ b/test_regress/t/t_lib_prot_delay_bad.v @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2019 by Todd Strader. +// SPDX-License-Identifier: CC0-1.0 + +module secret_impl; + initial begin + #10; + $stop; + end +endmodule diff --git a/test_regress/t/t_lib_prot_secret.v b/test_regress/t/t_lib_prot_secret.v index 7eeb7efca..1ed4dae83 100644 --- a/test_regress/t/t_lib_prot_secret.v +++ b/test_regress/t/t_lib_prot_secret.v @@ -46,7 +46,7 @@ module secret #(parameter GATED_CLK = 0) logic the_clk; generate if (GATED_CLK != 0) begin: yes_gated_clock - logic clk_en_latch /*verilator clock_enable*/; + logic clk_en_latch; /* verilator lint_off COMBDLY */ /* verilator lint_off LATCH */ always_comb if (clk == '0) clk_en_latch <= clk_en; diff --git a/test_regress/t/t_lib_prot_shared.pl b/test_regress/t/t_lib_prot_shared.pl index cc0c2f977..cd8b2a1c6 100755 --- a/test_regress/t/t_lib_prot_shared.pl +++ b/test_regress/t/t_lib_prot_shared.pl @@ -32,6 +32,7 @@ while (1) { cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", ($Self->{vltmt} ? ' --threads 6' : ''), + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -56,6 +57,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-LDFLAGS", "'-Wl,-rpath,$abs_secret_dir -L$abs_secret_dir -l$secret_prefix'"], xsim_flags2 => ["$secret_dir/secret.sv"], diff --git a/test_regress/t/t_lint_contassreg_bad.out b/test_regress/t/t_lint_contassreg_bad.out new file mode 100644 index 000000000..4921a2787 --- /dev/null +++ b/test_regress/t/t_lint_contassreg_bad.out @@ -0,0 +1,6 @@ +%Error-CONTASSREG: t/t_lint_contassreg_bad.v:14:11: Continuous assignment to reg, perhaps intended wire (IEEE 1364-2005 6.1; Verilog only, legal in SV): 'r' + : ... In instance t + 14 | assign r = 1'b0; + | ^ + ... For error description see https://verilator.org/warn/CONTASSREG?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_lint_contassreg_bad.pl b/test_regress/t/t_lint_contassreg_bad.pl new file mode 100755 index 000000000..46010fd1c --- /dev/null +++ b/test_regress/t/t_lint_contassreg_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ['--language 1364-2001'], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_contassreg_bad.v b/test_regress/t/t_lint_contassreg_bad.v new file mode 100644 index 000000000..585c37c0d --- /dev/null +++ b/test_regress/t/t_lint_contassreg_bad.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2012 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + + +module t(r); + + output r; + + reg r; + + assign r = 1'b0; // Bad + +endmodule diff --git a/test_regress/t/t_lint_didnotconverge_bad.out b/test_regress/t/t_lint_didnotconverge_bad.out index 1a0bb785a..f72c08997 100644 --- a/test_regress/t/t_lint_didnotconverge_bad.out +++ b/test_regress/t/t_lint_didnotconverge_bad.out @@ -1,6 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request --V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request_1 --V{t#,#} CHANGE: t/t_lint_didnotconverge_bad.v:14: a -%Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a) +%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_lint_didnotconverge_bad.pl b/test_regress/t/t_lint_didnotconverge_bad.pl index 5281eac77..6c153faa9 100755 --- a/test_regress/t/t_lint_didnotconverge_bad.pl +++ b/test_regress/t/t_lint_didnotconverge_bad.pl @@ -27,7 +27,7 @@ extract( extract( in => $Self->{golden_filename}, out => "../docs/gen/ex_DIDNOTCONVERGE_msg.rst", - lines => "2-5"); + lines => "1-2"); ok(1); 1; diff --git a/test_regress/t/t_lint_didnotconverge_bad.v b/test_regress/t/t_lint_didnotconverge_bad.v index 315e91aa5..544db7390 100644 --- a/test_regress/t/t_lint_didnotconverge_bad.v +++ b/test_regress/t/t_lint_didnotconverge_bad.v @@ -9,7 +9,7 @@ module t (/*AUTOARG*/ a, b ); - // verilator lint_off UNOPT + // verilator lint_off UNOPTFLAT output logic a, b; diff --git a/test_regress/t/t_lint_didnotconverge_nodbg_bad.out b/test_regress/t/t_lint_didnotconverge_nodbg_bad.out index 1b59dad18..b6e6359ab 100644 --- a/test_regress/t/t_lint_didnotconverge_nodbg_bad.out +++ b/test_regress/t/t_lint_didnotconverge_nodbg_bad.out @@ -1,2 +1,2 @@ -%Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge +%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_lint_edge_real.out b/test_regress/t/t_lint_edge_real.out new file mode 100644 index 000000000..bc9f0f0d5 --- /dev/null +++ b/test_regress/t/t_lint_edge_real.out @@ -0,0 +1,5 @@ +%Error: t/t_lint_edge_real.v:16:22: Edge event control not legal on real type (IEEE 1800-2017 6.12.1) + : ... In instance t + 16 | always @ (posedge rbad) $stop; + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_clk_condflop_nord.pl b/test_regress/t/t_lint_edge_real.pl similarity index 79% rename from test_regress/t/t_clk_condflop_nord.pl rename to test_regress/t/t_lint_edge_real.pl index 0c03448c5..07964a1b5 100755 --- a/test_regress/t/t_clk_condflop_nord.pl +++ b/test_regress/t/t_lint_edge_real.pl @@ -8,14 +8,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -scenarios(simulator => 1); +scenarios(vlt => 1); -compile( - verilator_flags2 => ["-no-order-clock-delay"], - ); - -execute( - check_finished => 1 +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, ); ok(1); diff --git a/test_regress/t/t_lint_edge_real.v b/test_regress/t/t_lint_edge_real.v new file mode 100644 index 000000000..58dfad8ad --- /dev/null +++ b/test_regress/t/t_lint_edge_real.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + rbad, rok + ); + input real rbad; + input real rok; + + always @ (rok) $stop; + + always @ (posedge rbad) $stop; + +endmodule diff --git a/test_regress/t/t_lint_historical.pl b/test_regress/t/t_lint_historical.pl new file mode 100755 index 000000000..a6e8d4033 --- /dev/null +++ b/test_regress/t/t_lint_historical.pl @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ["--lint-only"], + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_historical.v b/test_regress/t/t_lint_historical.v new file mode 100644 index 000000000..8647a070c --- /dev/null +++ b/test_regress/t/t_lint_historical.v @@ -0,0 +1,92 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + // Test all warnings, including those that are historically removed still parse + // verilator lint_off ALWCOMBORDER + // verilator lint_off ASSIGNDLY + // verilator lint_off ASSIGNIN + // verilator lint_off BADSTDPRAGMA + // verilator lint_off BLKANDNBLK + // verilator lint_off BLKLOOPINIT + // verilator lint_off BLKSEQ + // verilator lint_off BSSPACE + // verilator lint_off CASEINCOMPLETE + // verilator lint_off CASEOVERLAP + // verilator lint_off CASEWITHX + // verilator lint_off CASEX + // verilator lint_off CASTCONST + // verilator lint_off CDCRSTLOGIC + // verilator lint_off CLKDATA + // verilator lint_off CMPCONST + // verilator lint_off COLONPLUS + // verilator lint_off COMBDLY + // verilator lint_off CONTASSREG + // verilator lint_off DEFPARAM + // verilator lint_off DECLFILENAME + // verilator lint_off DEPRECATED + // verilator lint_off RISEFALLDLY + // verilator lint_off MINTYPMAXDLY + // verilator lint_off ENDLABEL + // verilator lint_off EOFNEWLINE + // verilator lint_off GENCLK + // verilator lint_off HIERBLOCK + // verilator lint_off IFDEPTH + // verilator lint_off IGNOREDRETURN + // verilator lint_off IMPERFECTSCH + // verilator lint_off IMPLICIT + // verilator lint_off IMPORTSTAR + // verilator lint_off IMPURE + // verilator lint_off INCABSPATH + // verilator lint_off INFINITELOOP + // verilator lint_off INITIALDLY + // verilator lint_off INSECURE + // verilator lint_off LATCH + // verilator lint_off LITENDIAN + // verilator lint_off MODDUP + // verilator lint_off MULTIDRIVEN + // verilator lint_off MULTITOP + // verilator lint_off NOLATCH + // verilator lint_off NULLPORT + // verilator lint_off PINCONNECTEMPTY + // verilator lint_off PINMISSING + // verilator lint_off PINNOCONNECT + // verilator lint_off PINNOTFOUND + // verilator lint_off PKGNODECL + // verilator lint_off PROCASSWIRE + // verilator lint_off PROFOUTOFDATE + // verilator lint_off PROTECTED + // verilator lint_off RANDC + // verilator lint_off REALCVT + // verilator lint_off REDEFMACRO + // verilator lint_off SELRANGE + // verilator lint_off SHORTREAL + // verilator lint_off SPLITVAR + // verilator lint_off STMTDLY + // verilator lint_off SYMRSVDWORD + // verilator lint_off SYNCASYNCNET + // verilator lint_off TICKCOUNT + // verilator lint_off TIMESCALEMOD + // verilator lint_off UNDRIVEN + // verilator lint_off UNOPT + // verilator lint_off UNOPTFLAT + // verilator lint_off UNOPTTHREADS + // verilator lint_off UNPACKED + // verilator lint_off UNSIGNED + // verilator lint_off UNUSEDGENVAR + // verilator lint_off UNUSEDPARAM + // verilator lint_off UNUSEDSIGNAL + // verilator lint_off USERERROR + // verilator lint_off USERFATAL + // verilator lint_off USERINFO + // verilator lint_off USERWARN + // verilator lint_off VARHIDDEN + // verilator lint_off WAITCONST + // verilator lint_off WIDTH + // verilator lint_off WIDTHCONCAT + // verilator lint_off ZERODLY + +endmodule diff --git a/test_regress/t/t_lint_iface_array_topmodule1.pl b/test_regress/t/t_lint_iface_array_topmodule1.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule1.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule1.v b/test_regress/t/t_lint_iface_array_topmodule1.v new file mode 100644 index 000000000..de19c13a2 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule1.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if; + + logic valid; + logic [7:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if [2], + my_if.master_mp out_if [2] + ); + + my_if my_i [2] (); + + always @(posedge clk) + begin + my_i[0].valid <= in_if[0].valid; + my_i[0].data <= in_if[0].data; + + my_i[1].valid <= in_if[1].valid; + my_i[1].data <= in_if[1].data; + end + + assign out_if[0].valid = my_i[0].valid; + assign out_if[0].data = my_i[0].data; + + assign out_if[1].valid = my_i[1].valid; + assign out_if[1].data = my_i[1].data; + +endmodule diff --git a/test_regress/t/t_lint_iface_array_topmodule2.pl b/test_regress/t/t_lint_iface_array_topmodule2.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule2.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule2.v b/test_regress/t/t_lint_iface_array_topmodule2.v new file mode 100644 index 000000000..078fb4495 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule2.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter DW = 8 + ) (); + + logic valid; + logic [DW-1:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if [2], + my_if.master_mp out_if [2] + ); + + assign out_if[0].valid = in_if[0].valid; + assign out_if[0].data = in_if[0].data; + + assign out_if[1].valid = in_if[1].valid; + assign out_if[1].data = in_if[1].data; + +endmodule diff --git a/test_regress/t/t_lint_iface_array_topmodule3.pl b/test_regress/t/t_lint_iface_array_topmodule3.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule3.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule3.v b/test_regress/t/t_lint_iface_array_topmodule3.v new file mode 100644 index 000000000..50dae10f2 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule3.v @@ -0,0 +1,77 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( parameter integer DW = 8 ) (input clk); + + localparam DW_LOCAL = DW; + + logic valid; + logic [DW-1:0] data; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + + function automatic integer width(); + return $bits(data); + endfunction + + generate + if (DW < 4) + begin: dw_lt_4_G + function automatic integer min_width(); + return 4; + endfunction + end + else + begin: dw_ge_4_G + function automatic integer min_width(); + return 8; + endfunction + end + endgenerate + +endinterface + +module t + ( + input wire clk, + my_if in_if [2], + my_if out_if [2] + ); + + assign out_if[0].valid = in_if[0].valid; + assign out_if[0].data = in_if[0].data; + + assign out_if[1].valid = in_if[1].valid; + assign out_if[1].data = in_if[1].data; + + my_if my_i (.clk(clk)); + + initial + begin + $display(in_if[0].DW_LOCAL); + $display(in_if[0].width()); + $display(in_if[0].dw_ge_4_G.min_width()); + $display(out_if[0].DW_LOCAL); + $display(out_if[0].width()); + $display(out_if[0].dw_ge_4_G.min_width()); + + $display(in_if[1].DW_LOCAL); + $display(in_if[1].width()); + $display(in_if[1].dw_ge_4_G.min_width()); + $display(out_if[1].DW_LOCAL); + $display(out_if[1].width()); + $display(out_if[1].dw_ge_4_G.min_width()); + end + +endmodule diff --git a/test_regress/t/t_lint_iface_array_topmodule_bad.out b/test_regress/t/t_lint_iface_array_topmodule_bad.out new file mode 100644 index 000000000..6c0980c74 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_lint_iface_array_topmodule_bad.v:8:24: Parameter without initial value is never given value (IEEE 1800-2017 6.20.1): 'DW' + : ... In instance t + 8 | parameter integer DW + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_iface_array_topmodule_bad.pl b/test_regress/t/t_lint_iface_array_topmodule_bad.pl new file mode 100755 index 000000000..a82cf66cb --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule_bad.v b/test_regress/t/t_lint_iface_array_topmodule_bad.v new file mode 100644 index 000000000..55b060591 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule_bad.v @@ -0,0 +1,50 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter integer DW + ) (); + + logic valid; + logic [7:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if [2], + my_if.master_mp out_if [2] + ); + + my_if my_i [2] (); + + always @(posedge clk) + begin + my_i[0].valid <= in_if[0].valid; + my_i[0].data <= in_if[0].data; + + my_i[1].valid <= in_if[1].valid; + my_i[1].data <= in_if[1].data; + end + + assign out_if[0].valid = my_i[0].valid; + assign out_if[0].data = my_i[0].data; + + assign out_if[1].valid = my_i[1].valid; + assign out_if[1].data = my_i[1].data; + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule1.pl b/test_regress/t/t_lint_iface_topmodule1.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule1.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule1.v b/test_regress/t/t_lint_iface_topmodule1.v new file mode 100644 index 000000000..4c4fa5b1f --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule1.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if; + + logic valid; + logic [7:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if, + my_if.master_mp out_if + ); + + my_if my_i (); + + always @(posedge clk) + begin + my_i.valid <= in_if.valid; + my_i.data <= in_if.data; + end + + assign out_if.valid = my_i.valid; + assign out_if.data = my_i.data; + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule2.pl b/test_regress/t/t_lint_iface_topmodule2.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule2.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule2.v b/test_regress/t/t_lint_iface_topmodule2.v new file mode 100644 index 000000000..ec836205a --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule2.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter integer DW = 8 + ) (); + logic valid; + logic [DW-1:0] data; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if, + my_if.master_mp out_if + ); + + assign out_if.valid = in_if.valid; + assign out_if.data = in_if.data; + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule3.pl b/test_regress/t/t_lint_iface_topmodule3.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule3.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule3.v b/test_regress/t/t_lint_iface_topmodule3.v new file mode 100644 index 000000000..a6084207b --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule3.v @@ -0,0 +1,67 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( parameter integer DW = 8 ) (input clk); + + localparam DW_LOCAL = DW; + + logic valid; + logic [DW-1:0] data; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + + function automatic integer width(); + return $bits(data); + endfunction + + generate + if (DW < 4) + begin: dw_lt_4_G + function automatic integer min_width(); + return 4; + endfunction + end + else + begin: dw_ge_4_G + function automatic integer min_width(); + return 8; + endfunction + end + endgenerate + +endinterface + +module t + ( + input wire clk, + my_if in_if, + my_if out_if + ); + + assign out_if.valid = in_if.valid; + assign out_if.data = in_if.data; + + my_if my_i (.clk(clk)); + + initial + begin + $display(in_if.DW_LOCAL); + $display(in_if.width()); + $display(in_if.dw_ge_4_G.min_width()); + $display(out_if.DW_LOCAL); + $display(out_if.width()); + $display(out_if.dw_ge_4_G.min_width()); + end + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule_bad.out b/test_regress/t/t_lint_iface_topmodule_bad.out new file mode 100644 index 000000000..b5263bb79 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_lint_iface_topmodule_bad.v:8:23: Parameter without initial value is never given value (IEEE 1800-2017 6.20.1): 'DW' + : ... In instance t + 8 | parameter integer DW + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_iface_topmodule_bad.pl b/test_regress/t/t_lint_iface_topmodule_bad.pl new file mode 100755 index 000000000..a82cf66cb --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule_bad.v b/test_regress/t/t_lint_iface_topmodule_bad.v new file mode 100644 index 000000000..04e51f7c3 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule_bad.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter integer DW + ) (); + + logic valid; + logic [DW-1:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if, + my_if.master_mp out_if + ); + + my_if my_i (); + + always @(posedge clk) + begin + my_i.valid <= in_if.valid; + my_i.data <= in_if.data; + end + + assign out_if.valid = my_i.valid; + assign out_if.data = my_i.data; + +endmodule diff --git a/test_regress/t/t_lint_latch_1.out b/test_regress/t/t_lint_latch_1.out deleted file mode 100644 index 22f8aceb5..000000000 --- a/test_regress/t/t_lint_latch_1.out +++ /dev/null @@ -1,9 +0,0 @@ -%Warning-COMBDLY: t/t_lint_latch_1.v:14:10: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 14 | o <= b; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. -%Error: Exiting due to diff --git a/test_regress/t/t_lint_latch_1.pl b/test_regress/t/t_lint_latch_1.pl index 07964a1b5..629a44bbb 100755 --- a/test_regress/t/t_lint_latch_1.pl +++ b/test_regress/t/t_lint_latch_1.pl @@ -11,8 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( - fails => 1, - expect_filename => $Self->{golden_filename}, ); ok(1); diff --git a/test_regress/t/t_lint_latch_4.v b/test_regress/t/t_lint_latch_4.v index e7182f4ca..9f5504dc0 100644 --- a/test_regress/t/t_lint_latch_4.v +++ b/test_regress/t/t_lint_latch_4.v @@ -7,21 +7,27 @@ module test ( input [2:0] a, input [3:0] c, - output reg [7:0] b + output reg [7:0] o1, + output reg [7:0] o2 ); - integer i; + integer i; - always @ (*) - begin - case(a) - {3'b000}: b = 8'd1; - {3'b001}: - for(i=0;i<4;i=i+1) b[i*2+:2] = 2'(c[i]); - {3'b010}: b = 8'd3; - {3'b011}: b = 8'd4; - default : b = 0; - endcase - end + always @ (*) begin + case(a) + {3'b000}: o1 = 8'd1; + {3'b001}: + for(i=0;i<4;i=i+1) o1[i*2+:2] = 2'(c[i]); + {3'b010}: o1 = 8'd3; + {3'b011}: o1 = 8'd4; + default : o1 = 0; + endcase + end + + always_comb begin + unique if (a[0]) o2 = 1; + else if (a[1]) o2 = 2; + else o2 = 3; + end endmodule diff --git a/test_regress/t/t_lint_latch_5.pl b/test_regress/t/t_lint_latch_5.pl index 07964a1b5..629a44bbb 100755 --- a/test_regress/t/t_lint_latch_5.pl +++ b/test_regress/t/t_lint_latch_5.pl @@ -11,8 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( - fails => 1, - expect_filename => $Self->{golden_filename}, ); ok(1); diff --git a/test_regress/t/t_lint_latch_5.v b/test_regress/t/t_lint_latch_5.v index 7cf29d05c..dcca92df8 100644 --- a/test_regress/t/t_lint_latch_5.v +++ b/test_regress/t/t_lint_latch_5.v @@ -10,10 +10,10 @@ module test always_latch if (e) - z[0] <= a[0]; + z[0] = a[0]; always_latch if (e) - z[1] <= a[1]; + z[1] = a[1]; endmodule diff --git a/test_regress/t/t_lint_latch_bad_2.out b/test_regress/t/t_lint_latch_bad_2.out index d5fb68aec..29e592071 100644 --- a/test_regress/t/t_lint_latch_bad_2.out +++ b/test_regress/t/t_lint_latch_bad_2.out @@ -1,13 +1,7 @@ -%Warning-COMBDLY: t/t_lint_latch_bad_2.v:13:10: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 13 | o <= b; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. %Warning-LATCH: t/t_lint_latch_bad_2.v:11:4: Latch inferred for signal 'o' (not all control paths of combinational always assign a value) : ... Suggest use of always_latch for intentional latches - 11 | always @(a or b) - | ^~~~~~ + 11 | always_comb + | ^~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/LATCH?v=latest + ... Use "/* verilator lint_off LATCH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_latch_bad_2.v b/test_regress/t/t_lint_latch_bad_2.v index e4719d0e0..e738dc23b 100644 --- a/test_regress/t/t_lint_latch_bad_2.v +++ b/test_regress/t/t_lint_latch_bad_2.v @@ -8,8 +8,8 @@ module t (/*AUTOARG*/ a, b, o); input b; output reg o; - always @(a or b) + always_comb if (a) - o <= b; + o = b; endmodule diff --git a/test_regress/t/t_lint_latch_bad_3.out b/test_regress/t/t_lint_latch_bad_3.out index b154019fa..cb89d481a 100644 --- a/test_regress/t/t_lint_latch_bad_3.out +++ b/test_regress/t/t_lint_latch_bad_3.out @@ -1,29 +1,7 @@ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:25:8: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 25 | o5 <= 1'b0; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:37:16: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 37 | o5 <= 1'b1; - | ^~ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:42:16: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 42 | o5 <= a; - | ^~ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:63:16: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 63 | o5 <= ~b; - | ^~ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:70:12: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 70 | o4 <= 1'b0; - | ^~ %Warning-LATCH: t/t_lint_latch_bad_3.v:18:1: Latch inferred for signal 'o5' (not all control paths of combinational always assign a value) : ... Suggest use of always_latch for intentional latches - 18 | always @(reset or en or a or b) - | ^~~~~~ + 18 | always_comb + | ^~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/LATCH?v=latest + ... Use "/* verilator lint_off LATCH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_latch_bad_3.v b/test_regress/t/t_lint_latch_bad_3.v index eb517e0de..83a87b16d 100644 --- a/test_regress/t/t_lint_latch_bad_3.v +++ b/test_regress/t/t_lint_latch_bad_3.v @@ -15,14 +15,14 @@ module t (/*AUTOARG*/ reset, a, b, c, en, o1, o2, o3, o4, o5); output reg o4; // " output reg o5; // Latch -always @(reset or en or a or b) +always_comb if (reset) begin o1 = 1'b0; o2 = 1'b0; o3 = 1'b0; o4 = 1'b0; - o5 <= 1'b0; // Do NOT expect Warning-COMBDLY + o5 = 1'b0; end else begin @@ -34,12 +34,12 @@ begin if (a) begin o3 = a; - o5 <= 1'b1; // Do NOT expect Warning-COMBDLY + o5 = 1'b1; end else begin o3 = ~a; - o5 <= a; // Do NOT expect Warning-COMBDLY + o5 = a; end // o3 is not assigned in either path of this if/else @@ -60,14 +60,14 @@ begin if (b) begin o3 = ~a | b; - o5 <= ~b; // Do NOT expect Warning-COMBDLY + o5 = ~b; end else begin o3 = a & ~b; // No assignment to o5, expect Warning-LATCH end - o4 <= 1'b0; // expect Warning-COMBDLY + o4 = 1'b0; end end diff --git a/test_regress/t/t_lint_nolatch_bad.out b/test_regress/t/t_lint_nolatch_bad.out index 764e77aaa..8a229bd07 100644 --- a/test_regress/t/t_lint_nolatch_bad.out +++ b/test_regress/t/t_lint_nolatch_bad.out @@ -1,12 +1,6 @@ -%Warning-COMBDLY: t/t_lint_nolatch_bad.v:13:10: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 13 | o <= b; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. %Warning-NOLATCH: t/t_lint_nolatch_bad.v:11:4: No latches detected in always_latch block - 11 | always_latch @(a or b) + 11 | always_latch | ^~~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/NOLATCH?v=latest + ... Use "/* verilator lint_off NOLATCH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_nolatch_bad.v b/test_regress/t/t_lint_nolatch_bad.v index 517f57f94..5f88a38c3 100644 --- a/test_regress/t/t_lint_nolatch_bad.v +++ b/test_regress/t/t_lint_nolatch_bad.v @@ -8,10 +8,10 @@ module t (/*AUTOARG*/ a, b, o); input b; output reg o; - always_latch @(a or b) + always_latch if (a) - o <= b; + o = b; else - o <= ~b; + o = ~b; endmodule diff --git a/test_regress/t/t_lint_once_bad.out b/test_regress/t/t_lint_once_bad.out index 04dca3c21..2f4ffa38d 100644 --- a/test_regress/t/t_lint_once_bad.out +++ b/test_regress/t/t_lint_once_bad.out @@ -1,11 +1,11 @@ -%Warning-UNUSED: t/t_lint_once_bad.v:19:14: Signal is not driven, nor used: 'unus1' - : ... In instance t.sub1 +%Warning-UNUSEDSIGNAL: t/t_lint_once_bad.v:19:14: Signal is not driven, nor used: 'unus1' + : ... In instance t.sub1 19 | reg [A:0] unus1; reg [A:0] unus2; | ^~~~~ - ... For warning description see https://verilator.org/warn/UNUSED?v=latest - ... Use "/* verilator lint_off UNUSED */" and lint_on around source to disable this message. -%Warning-UNUSED: t/t_lint_once_bad.v:19:34: Signal is not driven, nor used: 'unus2' - : ... In instance t.sub1 + ... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest + ... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message. +%Warning-UNUSEDSIGNAL: t/t_lint_once_bad.v:19:34: Signal is not driven, nor used: 'unus2' + : ... In instance t.sub1 19 | reg [A:0] unus1; reg [A:0] unus2; | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_lint_pragma_protected_bad.out b/test_regress/t/t_lint_pragma_protected_bad.out new file mode 100644 index 000000000..bd33b755d --- /dev/null +++ b/test_regress/t/t_lint_pragma_protected_bad.out @@ -0,0 +1,44 @@ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:8:17: Unknown '`pragma protect' error + 8 | `pragma protect encrypt_agent=123 + | ^~~~~~~~~~~~~~~~~ + ... For error description see https://verilator.org/warn/BADSTDPRAGMA?v=latest +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:10:17: Unknown '`pragma protect' error + 10 | `pragma protect encrypt_agent_info + | ^~~~~~~~~~~~~~~~~~ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:23:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:27:17: multiple `pragma protected encoding sections + 27 | `pragma protect encoding = (enctype = "BASE64", line_length = 76, bytes = 128) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Warning-PROTECTED: t/t_lint_pragma_protected_bad.v:44:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. + ... Use "/* verilator lint_off PROTECTED */" and lint_on around source to disable this message. +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:51:17: Illegal encoding type for `pragma protected encoding + 51 | `pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_lint_pragma_protected_bad.v:51:17: Unsupported: only BASE64 is recognized for `pragma protected encoding + 51 | `pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Warning-PROTECTED: t/t_lint_pragma_protected_bad.v:53:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:54:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 54 | c2lvbiAzIG9mIHRoZSBHTlUgTGVzc2VyCkdlbmVyYWwgUHVibGljIExpY2Vuc2UsIGFuZCB0aGUg + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:55:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 55 | IkdOVSBHUEwiIHJlZmVycyB0byB2ZXJzaW9uIDMgb2YgdGhlIEdOVQpHZW5lcmFsIFB1YmxpYyBM + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:56:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 56 | aWNlbnNlLgoKICAiVGhlIExpYnJhcnkiIHJlZmVycyB0byBhIGNvdmVyZWQgd29yayBnb3Zlcm5l + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:57:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 57 | ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:58:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 58 | ZCBXb3JrIGFzIG== + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:59:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:59:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block +%Warning-PROTECTED: t/t_lint_pragma_protected_bad.v:63:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:64:1: BASE64 line too long in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:64:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:72:1: `pragma is missing a pragma_expression. + 72 | `pragma + | ^~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_pragma_protected_err.pl b/test_regress/t/t_lint_pragma_protected_bad.pl similarity index 100% rename from test_regress/t/t_lint_pragma_protected_err.pl rename to test_regress/t/t_lint_pragma_protected_bad.pl diff --git a/test_regress/t/t_lint_pragma_protected_err.v b/test_regress/t/t_lint_pragma_protected_bad.v similarity index 76% rename from test_regress/t/t_lint_pragma_protected_err.v rename to test_regress/t/t_lint_pragma_protected_bad.v index 5e761c364..ca0099638 100644 --- a/test_regress/t/t_lint_pragma_protected_err.v +++ b/test_regress/t/t_lint_pragma_protected_bad.v @@ -48,6 +48,20 @@ aWNlbnNlLgoKICAiVGhlIExpYnJhcnkiIHJlZmVycyB0byBhIGNvdmVyZWQgd29yayBnb3Zlcm5l ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l ZCBXb3JrIGFzIG== +`pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) +`pragma protect data_block +aW5pdGlvbnMuCgogIEFzIHVzZWQgaGVyZWluLCAidGhpcyBMaWNlbnNlIiByZWZlcnMgdG8gdmVy +c2lvbiAzIG9mIHRoZSBHTlUgTGVzc2VyCkdlbmVyYWwgUHVibGljIExpY2Vuc2UsIGFuZCB0aGUg +IkdOVSBHUEwiIHJlZmVycyB0byB2ZXJzaW9uIDMgb2YgdGhlIEdOVQpHZW5lcmFsIFB1YmxpYyBM +aWNlbnNlLgoKICAiVGhlIExpYnJhcnkiIHJlZmVycyB0byBhIGNvdmVyZWQgd29yayBnb3Zlcm5l +ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l +ZCBXb3JrIGFzIG== + + +`pragma protect encoding = (enctype = "BASE64", line_length = 76, bytes = 76) +`pragma protect data_block +aW5pdGlvbnMuCgogIEFzIHVzZWQgaGVyZWluLCAidGhpcyBMaWNlbnNlIiByZWZlcnMgdG8gdmVyTOOLONG + `pragma protect end_protected diff --git a/test_regress/t/t_lint_pragma_protected_err.out b/test_regress/t/t_lint_pragma_protected_err.out deleted file mode 100644 index 2e5922f37..000000000 --- a/test_regress/t/t_lint_pragma_protected_err.out +++ /dev/null @@ -1,17 +0,0 @@ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:8:17: Unknown '`pragma protect' error - 8 | `pragma protect encrypt_agent=123 - | ^~~~~~~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/BADSTDPRAGMA?v=latest -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:10:17: Unknown '`pragma protect' error - 10 | `pragma protect encrypt_agent_info - | ^~~~~~~~~~~~~~~~~~ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:23:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:27:17: multiple `pragma protected encoding sections - 27 | `pragma protect encoding = (enctype = "BASE64", line_length = 76, bytes = 128) - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -%Warning-PROTECTED: t/t_lint_pragma_protected_err.v:44:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. - ... Use "/* verilator lint_off PROTECTED */" and lint_on around source to disable this message. -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:58:1: `pragma is missing a pragma_expression. - 58 | `pragma - | ^~~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_lint_stmtdly_bad.out b/test_regress/t/t_lint_stmtdly_bad.out index f23238765..c91e37476 100644 --- a/test_regress/t/t_lint_stmtdly_bad.out +++ b/test_regress/t/t_lint_stmtdly_bad.out @@ -1,7 +1,7 @@ -%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:8: Unsupported: Ignoring delay on this delayed statement. +%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:7: Ignoring delay on this statement due to --no-timing : ... In instance t 10 | #100 $finish; - | ^~~ + | ^ ... For warning description see https://verilator.org/warn/STMTDLY?v=latest ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_stmtdly_bad.pl b/test_regress/t/t_lint_stmtdly_bad.pl index 548dab0af..bcb7303da 100755 --- a/test_regress/t/t_lint_stmtdly_bad.pl +++ b/test_regress/t/t_lint_stmtdly_bad.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); compile( + verilator_flags2 => ["--no-timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_lint_unsup_mixed.v b/test_regress/t/t_lint_unsup_mixed.v index 9655b48b0..3681f56b3 100644 --- a/test_regress/t/t_lint_unsup_mixed.v +++ b/test_regress/t/t_lint_unsup_mixed.v @@ -31,4 +31,6 @@ module t qb = qb + 1; end + always @(posedge clk) $display("%d", qb); // So qb is not optimized away + endmodule diff --git a/test_regress/t/t_lint_unused_bad.out b/test_regress/t/t_lint_unused_bad.out index e8ebe7df3..4c2025413 100644 --- a/test_regress/t/t_lint_unused_bad.out +++ b/test_regress/t/t_lint_unused_bad.out @@ -1,39 +1,63 @@ -%Warning-UNUSED: t/t_lint_unused_bad.v:17:15: Bits of signal are not used: 'assunu1'[5:1] - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:17:15: Bits of signal are not used: 'assunu1'[5:1] + : ... In instance t.sub 17 | wire [5:0] assunu1 = 0; | ^~~~~~~ - ... For warning description see https://verilator.org/warn/UNUSED?v=latest - ... Use "/* verilator lint_off UNUSED */" and lint_on around source to disable this message. + ... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest + ... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message. %Warning-UNDRIVEN: t/t_lint_unused_bad.v:21:17: Bits of signal are not driven: 'udrb2'[14:13,11] : ... In instance t.sub 21 | wire [15:10] udrb2; | ^~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:26:15: Signal is not driven, nor used: 'unu3' - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:26:15: Signal is not driven, nor used: 'unu3' + : ... In instance t.sub 26 | wire unu3; | ^~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:28:15: Bits of signal are not driven, nor used: 'mixed'[3] - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:28:15: Bits of signal are not driven, nor used: 'mixed'[3] + : ... In instance t.sub 28 | wire [3:0] mixed; | ^~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:28:15: Bits of signal are not used: 'mixed'[2] - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:28:15: Bits of signal are not used: 'mixed'[2] + : ... In instance t.sub 28 | wire [3:0] mixed; | ^~~~~ %Warning-UNDRIVEN: t/t_lint_unused_bad.v:28:15: Bits of signal are not driven: 'mixed'[1] : ... In instance t.sub 28 | wire [3:0] mixed; | ^~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:37:14: Parameter is not used: 'UNUSED_P' - : ... In instance t.sub +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:37:14: Parameter is not used: 'UNUSED_P' + : ... In instance t.sub 37 | parameter UNUSED_P = 1; | ^~~~~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:38:15: Parameter is not used: 'UNUSED_LP' - : ... In instance t.sub +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:38:15: Parameter is not used: 'UNUSED_LP' + : ... In instance t.sub 38 | localparam UNUSED_LP = 2; | ^~~~~~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:40:15: Genvar is not driven, nor used: 'unused_gv' - : ... In instance t.sub +%Warning-UNUSEDGENVAR: t/t_lint_unused_bad.v:40:15: Genvar is not used: 'unused_gv' + : ... In instance t.sub 40 | genvar unused_gv; | ^~~~~~~~~ +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:45:15: Parameter is not used: 'linter_param1' + : ... In instance t.sub + 45 | localparam linter_param1 = 1; + | ^~~~~~~~~~~~~ +%Warning-UNUSEDGENVAR: t/t_lint_unused_bad.v:46:11: Genvar is not used: 'linter_genvar1' + : ... In instance t.sub + 46 | genvar linter_genvar1; + | ^~~~~~~~~~~~~~ +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:50:9: Signal is not driven, nor used: 'linter_sig2' + : ... In instance t.sub + 50 | wire linter_sig2; + | ^~~~~~~~~~~ +%Warning-UNUSEDGENVAR: t/t_lint_unused_bad.v:52:11: Genvar is not used: 'linter_genvar2' + : ... In instance t.sub + 52 | genvar linter_genvar2; + | ^~~~~~~~~~~~~~ +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:56:9: Signal is not driven, nor used: 'linter_sig3' + : ... In instance t.sub + 56 | wire linter_sig3; + | ^~~~~~~~~~~ +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:57:15: Parameter is not used: 'linter_param3' + : ... In instance t.sub + 57 | localparam linter_param3 = 3; + | ^~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_lint_unused_bad.v b/test_regress/t/t_lint_unused_bad.v index ade5152d3..cb2908289 100644 --- a/test_regress/t/t_lint_unused_bad.v +++ b/test_regress/t/t_lint_unused_bad.v @@ -40,6 +40,30 @@ module sub; genvar unused_gv; genvar ok_gv; + // verilator lint_off UNUSEDSIGNAL + wire linter_sig1; + localparam linter_param1 = 1; + genvar linter_genvar1; + // verilator lint_on UNUSEDSIGNAL + + // verilator lint_off UNUSEDPARAM + wire linter_sig2; + localparam linter_param2 = 2; + genvar linter_genvar2; + // verilator lint_on UNUSEDPARAM + + // verilator lint_off UNUSEDGENVAR + wire linter_sig3; + localparam linter_param3 = 3; + genvar linter_genvar3; + // verilator lint_on UNUSEDGENVAR + + // verilator lint_off UNUSED + wire linter_sig4; + localparam linter_param4 = 4; + genvar linter_genvar4; + // verilator lint_on UNUSED + initial begin if (0 && assunu1[0] != 0 && udrb2 != 0) begin end if (0 && assunub2[THREE] && assunub2[1:0]!=0) begin end diff --git a/test_regress/t/t_lint_unused_iface_bad.out b/test_regress/t/t_lint_unused_iface_bad.out index 2615f6433..ba0a9294e 100644 --- a/test_regress/t/t_lint_unused_iface_bad.out +++ b/test_regress/t/t_lint_unused_iface_bad.out @@ -4,8 +4,8 @@ | ^~~~~~~~ ... For warning description see https://verilator.org/warn/UNDRIVEN?v=latest ... Use "/* verilator lint_off UNDRIVEN */" and lint_on around source to disable this message. -%Warning-UNUSED: t/t_lint_unused_iface_bad.v:9:10: Signal is not used: 'sig_uusd' - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_iface_bad.v:9:10: Signal is not used: 'sig_uusd' + : ... In instance t.sub 9 | logic sig_uusd; | ^~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_lint_wait_bad.out b/test_regress/t/t_lint_wait_bad.out new file mode 100644 index 000000000..5ceecf26d --- /dev/null +++ b/test_regress/t/t_lint_wait_bad.out @@ -0,0 +1,12 @@ +%Warning-WAITCONST: t/t_timing_wait.v:47:13: Wait statement condition is constant + 47 | wait(0 < 1) $write("*-* All Finished *-*\n"); + | ^ + ... For warning description see https://verilator.org/warn/WAITCONST?v=latest + ... Use "/* verilator lint_off WAITCONST */" and lint_on around source to disable this message. +%Warning-WAITCONST: t/t_timing_wait.v:51:17: Wait statement condition is constant + 51 | initial wait(0) $stop; + | ^ +%Warning-WAITCONST: t/t_timing_wait.v:52:19: Wait statement condition is constant + 52 | initial wait(1 == 0) $stop; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_wait_bad.pl b/test_regress/t/t_lint_wait_bad.pl new file mode 100755 index 000000000..7500d4750 --- /dev/null +++ b/test_regress/t/t_lint_wait_bad.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +top_filename("t/t_timing_wait.v"); + +lint( + verilator_flags2 => ["--timing"], + expect_filename => $Self->{golden_filename}, + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_mailbox.out b/test_regress/t/t_mailbox.out index 4d5432212..d09756948 100644 --- a/test_regress/t/t_mailbox.out +++ b/test_regress/t/t_mailbox.out @@ -1,6 +1,6 @@ -%Error: t/t_mailbox.v:20:4: Can't find typedef: 'mailbox' - 20 | mailbox #(int) m; +%Error: t/t_mailbox.v:24:4: Can't find typedef: 'mailbox' + 24 | mailbox #(int) m; | ^~~~~~~ -%Error: Internal Error: t/t_mailbox.v:20:14: ../V3LinkDot.cpp:#: Pin not under instance? - 20 | mailbox #(int) m; +%Error: Internal Error: t/t_mailbox.v:24:14: ../V3LinkDot.cpp:#: Pin not under instance? + 24 | mailbox #(int) m; | ^~~ diff --git a/test_regress/t/t_mailbox.v b/test_regress/t/t_mailbox.v index 06ea1f834..64e2ba175 100644 --- a/test_regress/t/t_mailbox.v +++ b/test_regress/t/t_mailbox.v @@ -16,8 +16,12 @@ // function int try_peek( ref T message ); // endclass +`ifndef MAILBOX_T + `define MAILBOX_T mailbox +`endif + module t(/*AUTOARG*/); - mailbox #(int) m; + `MAILBOX_T #(int) m; int msg; int out; @@ -32,7 +36,7 @@ module t(/*AUTOARG*/); if (m.num() != 1) $stop; if (m.try_peek(out) <= 0) $stop; if (out != 123) $stop; - if (m.num() != 0) $stop; + if (m.num() != 1) $stop; out = 0; if (m.try_peek(out) <= 0) $stop; if (out != 123) $stop; @@ -50,8 +54,8 @@ module t(/*AUTOARG*/); msg = 125; m.put(msg); m.put(msg); - m.try_put(msg); - m.try_put(msg); + if (m.try_put(msg) == 0) $stop; + if (m.try_put(msg) == 0) $stop; if (m.num() != 4) $stop; if (m.try_put(msg) != 0) $stop; if (m.num() != 4) $stop; diff --git a/test_regress/t/t_mailbox_bad.out b/test_regress/t/t_mailbox_bad.out index 4d5432212..218a34ad3 100644 --- a/test_regress/t/t_mailbox_bad.out +++ b/test_regress/t/t_mailbox_bad.out @@ -1,6 +1,6 @@ -%Error: t/t_mailbox.v:20:4: Can't find typedef: 'mailbox' - 20 | mailbox #(int) m; +%Error: t/t_mailbox_bad.v:8:4: Can't find typedef: 'mailbox' + 8 | mailbox #(int) m; | ^~~~~~~ -%Error: Internal Error: t/t_mailbox.v:20:14: ../V3LinkDot.cpp:#: Pin not under instance? - 20 | mailbox #(int) m; +%Error: Internal Error: t/t_mailbox_bad.v:8:14: ../V3LinkDot.cpp:#: Pin not under instance? + 8 | mailbox #(int) m; | ^~~ diff --git a/test_regress/t/t_mailbox_bad.pl b/test_regress/t/t_mailbox_bad.pl index 8de551634..a083f46f5 100755 --- a/test_regress/t/t_mailbox_bad.pl +++ b/test_regress/t/t_mailbox_bad.pl @@ -10,10 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -top_filename("t_mailbox.v"); - lint( - verilator_flags2 => ["--xml-only"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_mailbox_class.pl b/test_regress/t/t_mailbox_class.pl new file mode 100755 index 000000000..3329d516e --- /dev/null +++ b/test_regress/t/t_mailbox_class.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--timing"], + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_mailbox_class.v b/test_regress/t/t_mailbox_class.v new file mode 100644 index 000000000..31619225c --- /dev/null +++ b/test_regress/t/t_mailbox_class.v @@ -0,0 +1,65 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +class mailbox_cls #(type T=int); + // Test an implementation similar to what Verilator will do internally + int m_bound; + T m_q[$]; + + function new(int bound = 0); + m_bound = bound; + endfunction + + function int num(); + return m_q.size(); + endfunction + + task put(T message); + if (m_bound != 0) wait (m_q.size() < m_bound); + m_q.push_back(message); + endtask + function int try_put(T message); + if (m_bound != 0 && m_q.size() < m_bound) begin + m_q.push_back(message); + return 1; + end + else begin + return 0; + end + endfunction + + task get(ref T message); + wait (m_q.size() != 0); + message = m_q.pop_front(); + endtask + function int try_get(ref T message); + if (m_q.size() != 0) begin + message = m_q.pop_front(); + return 1; + end + else begin + return 0; + end + endfunction + + task peek(ref T message); + wait (m_q.size() != 0); + message = m_q[0]; + endtask + function int try_peek(ref T message); + if (m_q.size() != 0) begin + message = m_q[0]; + return 1; + end + else begin + return 0; + end + endfunction +endclass + +`define MAILBOX_T mailbox_cls + +`include "t_mailbox.v" diff --git a/test_regress/t/t_mailbox_std.out b/test_regress/t/t_mailbox_std.out new file mode 100644 index 000000000..d9c517f02 --- /dev/null +++ b/test_regress/t/t_mailbox_std.out @@ -0,0 +1,11 @@ +%Error: t/t_mailbox.pl:1:1: syntax error, unexpected '#' + 1 | #!/usr/bin/env perl + | ^ +%Error-UNSUPPORTED: t/t_mailbox.pl:2:19: Unsupported: Verilog 2001-config reserved word not implemented: 'use' + 2 | if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } + | ^~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_mailbox.pl:3:47: Unsupported: SystemVerilog 2005 reserved word not implemented: 'expect' + 3 | # DESCRIPTION: Verilator: Verilog Test driver/expect definition + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_mailbox_std.pl b/test_regress/t/t_mailbox_std.pl new file mode 100755 index 000000000..207b5f005 --- /dev/null +++ b/test_regress/t/t_mailbox_std.pl @@ -0,0 +1,26 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_mailbox.pl"); + +compile( + v_flags2 => ["+define+T_MAILBOX+std::mailbox"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_math_eq_bad.out b/test_regress/t/t_math_eq_bad.out new file mode 100644 index 000000000..d428d8f50 --- /dev/null +++ b/test_regress/t/t_math_eq_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_math_eq_bad.v:13:13: Real is illegal operand to ?== operator + : ... In instance t + 13 | if (a ==? 1.0) $stop; + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_math_eq_bad.pl b/test_regress/t/t_math_eq_bad.pl new file mode 100755 index 000000000..bce2416ed --- /dev/null +++ b/test_regress/t/t_math_eq_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2010 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + expect_filename => $Self->{golden_filename}, + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_math_eq_bad.v b/test_regress/t/t_math_eq_bad.v new file mode 100644 index 000000000..a54b3acae --- /dev/null +++ b/test_regress/t/t_math_eq_bad.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + logic [31:0] a; + + initial begin + a = 1234; + if (a ==? 1.0) $stop; // Bad + end + +endmodule diff --git a/test_regress/t/t_math_repl_bad.out b/test_regress/t/t_math_repl_bad.out new file mode 100644 index 000000000..49249a807 --- /dev/null +++ b/test_regress/t/t_math_repl_bad.out @@ -0,0 +1,22 @@ +%Error: t/t_math_repl_bad.v:12:14: Replication value of 0 is only legal under a concatenation (IEEE 1800-2017 11.4.12.1) + : ... In instance t + 12 | o = {0 {1'b1}}; + | ^ +%Warning-WIDTH: t/t_math_repl_bad.v:12:9: Operator ASSIGN expects 32 bits on the Assign RHS, but Assign RHS's REPLICATE generates 1 bits. + : ... In instance t + 12 | o = {0 {1'b1}}; + | ^ + ... For warning description see https://verilator.org/warn/WIDTH?v=latest + ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. +%Error: t/t_math_repl_bad.v:13:12: Expecting expression to be constant, but can't convert a TESTPLUSARGS to constant. + : ... In instance t + 13 | o = {$test$plusargs("NON-CONSTANT") {1'b1}}; + | ^~~~~~~~~~~~~~ +%Error: t/t_math_repl_bad.v:13:43: Replication value isn't a constant. + : ... In instance t + 13 | o = {$test$plusargs("NON-CONSTANT") {1'b1}}; + | ^ +%Error: Internal Error: t/t_math_repl_bad.v:13:9: ../V3Width.cpp:#: Node has no type + : ... In instance t + 13 | o = {$test$plusargs("NON-CONSTANT") {1'b1}}; + | ^ diff --git a/test_regress/t/t_math_repl_bad.pl b/test_regress/t/t_math_repl_bad.pl new file mode 100755 index 000000000..9c9fb65a0 --- /dev/null +++ b/test_regress/t/t_math_repl_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2010 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_math_repl_bad.v b/test_regress/t/t_math_repl_bad.v new file mode 100644 index 000000000..788c7239c --- /dev/null +++ b/test_regress/t/t_math_repl_bad.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + logic [31:0] o; + + initial begin + o = {0 {1'b1}}; // Bad 0 rep + o = {$test$plusargs("NON-CONSTANT") {1'b1}}; // Bad non-constant rep + $stop; + end +endmodule diff --git a/test_regress/t/t_math_shift.pl b/test_regress/t/t_math_shift.pl index 757e26425..b46d46042 100755 --- a/test_regress/t/t_math_shift.pl +++ b/test_regress/t/t_math_shift.pl @@ -11,7 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA"] ); execute( diff --git a/test_regress/t/t_math_shift_noexpand.pl b/test_regress/t/t_math_shift_noexpand.pl index 8584197a0..e27343a72 100755 --- a/test_regress/t/t_math_shift_noexpand.pl +++ b/test_regress/t/t_math_shift_noexpand.pl @@ -13,7 +13,7 @@ scenarios(vlt => 1); top_filename("t/t_math_shift.v"); compile( - verilator_flags2 => ["-Wno-CLKDATA", '-fno-expand'], + verilator_flags2 => ['-fno-expand'], ); execute( diff --git a/test_regress/t/t_math_signed5.pl b/test_regress/t/t_math_signed5.pl index b46d46042..f5debdb98 100755 --- a/test_regress/t/t_math_signed5.pl +++ b/test_regress/t/t_math_signed5.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ['--no-timing'], ); execute( diff --git a/test_regress/t/t_math_signed5_timing.pl b/test_regress/t/t_math_signed5_timing.pl new file mode 100755 index 000000000..2365fef9a --- /dev/null +++ b/test_regress/t/t_math_signed5_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_math_signed5.v"); + + compile( + verilator_flags2 => ['--timing'], + timing_loop => 1, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_math_wide_bad.out b/test_regress/t/t_math_wide_bad.out index ec2179c13..b3c120d82 100644 --- a/test_regress/t/t_math_wide_bad.out +++ b/test_regress/t/t_math_wide_bad.out @@ -1,10 +1,10 @@ -%Error-UNSUPPORTED: t/t_math_wide_bad.v:22:18: Unsupported: operator POWSS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h - 22 | assign z2 = a ** 3; - | ^~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_math_wide_bad.v:23:15: Unsupported: operator ISTORD operator of 64 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h 23 | assign r = real'(a); | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_math_wide_bad.v:22:18: Unsupported: operator POWSS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h + 22 | assign z2 = a ** 3; + | ^~ %Error-UNSUPPORTED: t/t_math_wide_bad.v:21:17: Unsupported: operator MULS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h 21 | assign z = a * b; | ^ diff --git a/test_regress/t/t_mem_slot.pl b/test_regress/t/t_mem_slot.pl index 19193485f..9e97f1977 100755 --- a/test_regress/t/t_mem_slot.pl +++ b/test_regress/t/t_mem_slot.pl @@ -13,7 +13,7 @@ scenarios(vlt_all => 1); compile( make_top_shell => 0, make_main => 0, - verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp"], + verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp --no-timing"], ); execute( diff --git a/test_regress/t/t_mod_dollar$.pl b/test_regress/t/t_mod_dollar$.pl index 2d8172ca7..7441e7b09 100755 --- a/test_regress/t/t_mod_dollar$.pl +++ b/test_regress/t/t_mod_dollar$.pl @@ -14,7 +14,7 @@ scenarios(vlt => 1); # prefix properly using post-escaped identifiers run(cmd => ["../bin/verilator", "--cc", - "--Mdir obj_vlt/t_mod_dollar", + "--Mdir " . $Self->{obj_dir} . "/t_mod_dollar", "--exe --build --main", 't/t_mod_dollar$.v', ], diff --git a/test_regress/t/t_net_delay.out b/test_regress/t/t_net_delay.out new file mode 100644 index 000000000..b11072c2e --- /dev/null +++ b/test_regress/t/t_net_delay.out @@ -0,0 +1,7 @@ +%Warning-ASSIGNDLY: t/t_net_delay.v:17:11: Ignoring timing control on this assignment/primitive due to --no-timing + : ... In instance t + 17 | assign #4 val2 = cyc; + | ^ + ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest + ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_timing_net_delay.pl b/test_regress/t/t_net_delay.pl similarity index 89% rename from test_regress/t/t_timing_net_delay.pl rename to test_regress/t/t_net_delay.pl index d61820774..09ee1bbae 100755 --- a/test_regress/t/t_timing_net_delay.pl +++ b/test_regress/t/t_net_delay.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], + verilator_flags2 => ['-Wall -Wno-DECLFILENAME --no-timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_timing_net_delay.v b/test_regress/t/t_net_delay.v similarity index 91% rename from test_regress/t/t_timing_net_delay.v rename to test_regress/t/t_net_delay.v index aa125ca8c..c9b524005 100644 --- a/test_regress/t/t_timing_net_delay.v +++ b/test_regress/t/t_net_delay.v @@ -21,7 +21,7 @@ module t (/*AUTOARG*/ `ifdef TEST_VERBOSE $write("[%0t] cyc=%0d, val1=%0d, val2=%0d\n", $time, cyc, val1, val2); `endif - if (cyc >= 4 && val1 != cyc-1 && val2 != cyc-3) $stop; + if (cyc >= 7 && val1 != cyc-1 && val2 != cyc-7) $stop; if (cyc == 15) begin $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_net_delay_timing.pl b/test_regress/t/t_net_delay_timing.pl new file mode 100755 index 000000000..63a94fdbd --- /dev/null +++ b/test_regress/t/t_net_delay_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_net_delay.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_net_delay_timing_sc.pl b/test_regress/t/t_net_delay_timing_sc.pl new file mode 100755 index 000000000..1fd75cffb --- /dev/null +++ b/test_regress/t/t_net_delay_timing_sc.pl @@ -0,0 +1,34 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 2; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +elsif (!$Self->have_sc) { + skip("No SystemC installed"); +} +else { + top_filename("t/t_net_delay.v"); + + compile( + verilator_flags2 => ["--sc --exe --timing --timescale 10ps/1ps"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_notiming.out b/test_regress/t/t_notiming.out new file mode 100644 index 000000000..b625df546 --- /dev/null +++ b/test_regress/t/t_notiming.out @@ -0,0 +1,33 @@ +%Warning-STMTDLY: t/t_notiming.v:12:8: Ignoring delay on this statement due to --no-timing + : ... In instance t + 12 | #1 + | ^ + ... For warning description see https://verilator.org/warn/STMTDLY?v=latest + ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. +%Error-NOTIMING: t/t_notiming.v:13:8: Fork statements require --timing + : ... In instance t + 13 | fork @e; @e; join; + | ^~~~ +%Error-NOTIMING: t/t_notiming.v:14:8: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 14 | @e + | ^ +%Error-NOTIMING: t/t_notiming.v:15:8: Wait statements require --timing + : ... In instance t + 15 | wait(x == 4) + | ^~~~ +%Error-NOTIMING: t/t_notiming.v:19:8: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 19 | @e + | ^ +%Warning-STMTDLY: t/t_notiming.v:26:12: Ignoring delay on this statement due to --no-timing + : ... In instance t + 26 | initial #1 ->e; + | ^ +%Warning-STMTDLY: t/t_notiming.v:27:12: Ignoring delay on this statement due to --no-timing + : ... In instance t + 27 | initial #2 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_event.pl b/test_regress/t/t_notiming.pl similarity index 90% rename from test_regress/t/t_timing_intra_assign_event.pl rename to test_regress/t/t_notiming.pl index d61820774..5a683788e 100755 --- a/test_regress/t/t_timing_intra_assign_event.pl +++ b/test_regress/t/t_notiming.pl @@ -10,8 +10,8 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], +compile( + verilator_flags2 => ["--no-timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_notiming.v b/test_regress/t/t_notiming.v new file mode 100644 index 000000000..649888e1f --- /dev/null +++ b/test_regress/t/t_notiming.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e; + + initial begin + int x; + #1 + fork @e; @e; join; + @e + wait(x == 4) + x = #1 8; + if (x != 8) $stop; + if ($time != 0) $stop; + @e + if (!e.triggered) $stop; + if ($time != 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + + initial #1 ->e; + initial #2 $stop; // timeout +endmodule + +`ifdef VERILATOR_TIMING +`error "VERILATOR_TIMING should not be defined with --no-timing" +`endif diff --git a/test_regress/t/t_notiming_off.out b/test_regress/t/t_notiming_off.out new file mode 100644 index 000000000..72c86fd65 --- /dev/null +++ b/test_regress/t/t_notiming_off.out @@ -0,0 +1,29 @@ +%Error-NOTIMING: t/t_timing_off.v:25:8: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 25 | @e1; + | ^ + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Warning-STMTDLY: t/t_timing_off.v:33:12: Ignoring delay on this statement due to --no-timing + : ... In instance t + 33 | initial #2 ->e1; + | ^ + ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. +%Warning-STMTDLY: t/t_timing_off.v:37:12: Ignoring delay on this statement due to --no-timing + : ... In instance t + 37 | initial #3 $stop; + | ^ +%Warning-STMTDLY: t/t_timing_off.v:38:12: Ignoring delay on this statement due to --no-timing + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error-NOTIMING: t/t_timing_off.v:38:15: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Warning-STMTDLY: t/t_timing_off.v:38:25: Ignoring delay on this statement due to --no-timing + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_notiming_off.pl b/test_regress/t/t_notiming_off.pl new file mode 100755 index 000000000..cca2c7651 --- /dev/null +++ b/test_regress/t/t_notiming_off.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_timing_off.v"); + +compile( + verilator_flags2 => ["--no-timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_optm_if_cond.pl b/test_regress/t/t_optm_if_cond.pl index 7910f570f..3215fbf08 100755 --- a/test_regress/t/t_optm_if_cond.pl +++ b/test_regress/t/t_optm_if_cond.pl @@ -15,7 +15,7 @@ compile( ); if ($Self->{vlt_all}) { - file_grep($Self->{stats}, qr/Node count, IF +\d+ +\d+ +\d+ +\d+ +(\d+)/, 11); + file_grep($Self->{stats}, qr/Node count, IF +\d+ +\d+ +\d+ +\d+ +(\d+)/, 28); } ok(1); diff --git a/test_regress/t/t_order.pl b/test_regress/t/t_order.pl index b46d46042..800b5bdc4 100755 --- a/test_regress/t/t_order.pl +++ b/test_regress/t/t_order.pl @@ -10,7 +10,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); +$Self->{main_time_multiplier} = 1e-8 / 1e-9; + compile( + verilator_flags2 => ["--timescale 10ns/1ns --no-timing"], ); execute( diff --git a/test_regress/t/t_order_blkandnblk_bad.out b/test_regress/t/t_order_blkandnblk_bad.out index 709176366..e2bcffd36 100644 --- a/test_regress/t/t_order_blkandnblk_bad.out +++ b/test_regress/t/t_order_blkandnblk_bad.out @@ -1,11 +1,11 @@ %Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:17:21: Unsupported: Blocked and non-blocking assignments to same variable: 't.array' 17 | logic [1:0][3:0] array; | ^~~~~ - t/t_order_blkandnblk_bad.v:19:16: ... Location of blocking assignment + t/t_order_blkandnblk_bad.v:19:25: ... Location of blocking assignment 19 | always_comb array[0] = i; - | ^~~~~ - t/t_order_blkandnblk_bad.v:22:6: ... Location of nonblocking assignment + | ^ + t/t_order_blkandnblk_bad.v:22:15: ... Location of nonblocking assignment 22 | array[1] <= array[0]; - | ^~~~~ + | ^~ ... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_order_clkinst.out b/test_regress/t/t_order_clkinst.out index 98e8e114b..9e56d1ba0 100644 --- a/test_regress/t/t_order_clkinst.out +++ b/test_regress/t/t_order_clkinst.out @@ -62,7 +62,7 @@ b00000010 $ b00000000000000000000000000000011 % b00000000000000000000000000000011 & 1' -b00000000000000000000000000000101 ( +b00000000000000000000000000000011 ( 1/ #25 0/ diff --git a/test_regress/t/t_order_clkinst.v b/test_regress/t/t_order_clkinst.v index 048f5851b..a54fa9397 100644 --- a/test_regress/t/t_order_clkinst.v +++ b/test_regress/t/t_order_clkinst.v @@ -15,6 +15,7 @@ module t (/*AUTOARG*/ // verilator lint_off UNOPT // verilator lint_off UNOPTFLAT // verilator lint_off BLKANDNBLK + // verilator lint_off MULTIDRIVEN reg c1_start; initial c1_start = 0; wire [31:0] c1_count; @@ -49,7 +50,7 @@ module t (/*AUTOARG*/ // should reach the normal '$finish' below on the next cycle. if (c1_count!=32'h3) $stop; if (s2_count!=32'h3) $stop; - if (c3_count!=32'h5) $stop; + if (c3_count!=32'h3) $stop; end 8'd03: begin $write("*-* All Finished *-*\n"); @@ -67,11 +68,12 @@ module comb_loop (/*AUTOARG*/ start ); input start; - output reg [31:0] count; initial count = 0; + output reg [31:0] count = 0; reg [31:0] runnerm1, runner; initial runner = 0; always @ (posedge start) begin + count = 0; runner = 3; end @@ -101,6 +103,7 @@ module seq_loop (/*AUTOARG*/ reg [31:0] runnerm1, runner; initial runner = 0; always @ (posedge start) begin + count = 0; runner <= 3; end diff --git a/test_regress/t/t_order_clkinst_bad.out b/test_regress/t/t_order_clkinst_bad.out deleted file mode 100644 index 843d81b45..000000000 --- a/test_regress/t/t_order_clkinst_bad.out +++ /dev/null @@ -1,24 +0,0 @@ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:19:16: Imperfect scheduling of variable: 't.c1_start' - 19 | reg c1_start; initial c1_start = 0; - | ^~~~~~~~ - ... For warning description see https://verilator.org/warn/IMPERFECTSCH?v=latest - ... Use "/* verilator lint_off IMPERFECTSCH */" and lint_on around source to disable this message. -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:20:16: Imperfect scheduling of variable: 't.c1_count' - 20 | wire [31:0] c1_count; - | ^~~~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:24:16: Imperfect scheduling of variable: 't.s2_count' - 24 | wire [31:0] s2_count; - | ^~~~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:28:16: Imperfect scheduling of variable: 't.c3_count' - 28 | wire [31:0] c3_count; - | ^~~~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:72:28: Imperfect scheduling of variable: 't.c1.runner' - 72 | reg [31:0] runnerm1, runner; initial runner = 0; - | ^~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:101:28: Imperfect scheduling of variable: 't.s2.runner' - 101 | reg [31:0] runnerm1, runner; initial runner = 0; - | ^~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:72:28: Imperfect scheduling of variable: 't.c3.runner' - 72 | reg [31:0] runnerm1, runner; initial runner = 0; - | ^~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_order_doubleloop.pl b/test_regress/t/t_order_doubleloop.pl index 1f8ac0132..ce1f8586f 100755 --- a/test_regress/t/t_order_doubleloop.pl +++ b/test_regress/t/t_order_doubleloop.pl @@ -10,14 +10,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -my $fail = $Self->{vlt_all}; - compile( ); execute( - check_finished => !$fail, - fails => $fail, + check_finished => 1 ); ok(1); diff --git a/test_regress/t/t_order_dpi_export_6.cpp b/test_regress/t/t_order_dpi_export_6.cpp new file mode 100644 index 000000000..95fceb8ef --- /dev/null +++ b/test_regress/t/t_order_dpi_export_6.cpp @@ -0,0 +1,37 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include +#include +#include + +void toggle_other_clk(svBit val) { set_other_clk(val); } + +int main(int argc, char* argv[]) { + Vt_order_dpi_export_6* const tb = new Vt_order_dpi_export_6; + tb->contextp()->commandArgs(argc, argv); + bool clk = true; + + while (!tb->contextp()->gotFinish()) { + // Timeout + if (tb->contextp()->time() > 100000) break; + // Toggle and set main clock + clk = !clk; + tb->clk = clk; + // Eval + tb->eval(); + // Advance time + tb->contextp()->timeInc(500); + } + + delete tb; + return 0; +} diff --git a/test_regress/t/t_order_dpi_export_6.pl b/test_regress/t/t_order_dpi_export_6.pl new file mode 100755 index 000000000..e76a9afcd --- /dev/null +++ b/test_regress/t/t_order_dpi_export_6.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + make_top_shell => 0, + make_main => 0, + verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_order_dpi_export_6.v b/test_regress/t/t_order_dpi_export_6.v new file mode 100644 index 000000000..36a33d141 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_6.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module testbench( + /*AUTOARG*/ + // Inputs + clk + ); + + input clk; // Top level input clock + logic other_clk; // Dependent clock set via DPI + + export "DPI-C" function set_other_clk; + function void set_other_clk(bit val); + other_clk = val; + endfunction; + + bit even_other = 1; + bit current_even_other = 1; + import "DPI-C" context function void toggle_other_clk(bit val); + always @(posedge clk) begin + even_other <= ~even_other; + current_even_other = even_other; + toggle_other_clk(even_other); + end + + int n = 0; + + always @(edge other_clk) begin + // This always block needs to evaluate before the NBA to even_other + // above is committed, as setting clocks via the set_other_clk uses + // blocking assignment. + if (even_other !== current_even_other) $stop; + $display("t=%t n=%d", $time, n); + if ($time != (2*n+1) * 500) $stop; + if (n == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + n += 1; + end + +endmodule diff --git a/test_regress/t/t_order_dpi_export_7.cpp b/test_regress/t/t_order_dpi_export_7.cpp new file mode 100644 index 000000000..6034549d4 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_7.cpp @@ -0,0 +1,36 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include +#include +#include + +int main(int argc, char* argv[]) { + Vt_order_dpi_export_7* const tb = new Vt_order_dpi_export_7; + tb->contextp()->commandArgs(argc, argv); + bool clk = true; + + while (!tb->contextp()->gotFinish()) { + // Timeout + if (tb->contextp()->time() > 100000) break; + // Toggle and set clock + svSetScope(svGetScopeFromName("TOP.testbench")); + clk = !clk; + set_inputs(clk); + // Eval + tb->eval(); + // Advance time + tb->contextp()->timeInc(500); + } + + delete tb; + return 0; +} diff --git a/test_regress/t/t_order_dpi_export_7.pl b/test_regress/t/t_order_dpi_export_7.pl new file mode 100755 index 000000000..e76a9afcd --- /dev/null +++ b/test_regress/t/t_order_dpi_export_7.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + make_top_shell => 0, + make_main => 0, + verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_order_dpi_export_7.v b/test_regress/t/t_order_dpi_export_7.v new file mode 100644 index 000000000..575b88b50 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_7.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module testbench; + + logic clk; + logic data; + + export "DPI-C" function set_inputs; + function void set_inputs(bit val); + clk = val; + data = val; + endfunction; + + // This needs to be in the 'ico' region. Written with $c1 to prevent + // gate optimization. + wire invdata = $c1(1) ^ data; + + int n = 0; + + always @(edge clk) begin + // The combinational update needs to have take effect (in the 'ico' + // region), before this always block is executed + if (invdata != ~data) $stop; + $display("t=%t n=%d", $time, n); + if ($time != (1*n+1) * 500) $stop; + if (n == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + n += 1; + end + +endmodule diff --git a/test_regress/t/t_order_dpi_export_8.cpp b/test_regress/t/t_order_dpi_export_8.cpp new file mode 100644 index 000000000..824aa58bf --- /dev/null +++ b/test_regress/t/t_order_dpi_export_8.cpp @@ -0,0 +1,15 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include +#include + +void call_set_x(svBit val) { set_x(val); } diff --git a/test_regress/t/t_order_dpi_export_8.pl b/test_regress/t/t_order_dpi_export_8.pl new file mode 100755 index 000000000..2c9b6e9f0 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_8.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_order_dpi_export_8.v b/test_regress/t/t_order_dpi_export_8.v new file mode 100644 index 000000000..2df1a3e9b --- /dev/null +++ b/test_regress/t/t_order_dpi_export_8.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module testbench( + /*AUTOARG*/ + // Inputs + clk + ); + + input clk; // Top level input clock + + bit x = 0; + + wire y = x & $c(1); + + export "DPI-C" function set_x; + function void set_x(bit val); + x = val; + endfunction; + + import "DPI-C" context function void call_set_x(bit val); + + bit q = 0; + always @(posedge clk) q <= ~q; + + always @(edge q) call_set_x(q); + + int n = 0; + + always @(edge clk) begin + // This always block needs to evaluate before the NBA to even_other + // above is committed, as setting clocks via the set_other_clk uses + // blocking assignment. + $display("t=%t q=%d x=%d y=%d", $time, q, x, y); + if (y !== q) $stop; + if (n == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + n += 1; + end + +endmodule diff --git a/test_regress/t/t_order_loop_bad.pl b/test_regress/t/t_order_loop_bad.pl index 3d2c637a8..c35bc85ef 100755 --- a/test_regress/t/t_order_loop_bad.pl +++ b/test_regress/t/t_order_loop_bad.pl @@ -10,15 +10,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -lint( - fails => 1, - # Can't use expect_filename here as unstable output - expect => -'%Error: Circular logic when ordering code .* - *t/t_order_loop_bad.v:\d+:\d+: + Example path: ALWAYS - *t/t_order_loop_bad.v:\d+:\d+: + Example path: t.ready - *t/t_order_loop_bad.v:\d+:\d+: + Example path: ACTIVE -.*', +compile(); + +execute( + check_finished => 1, ); ok(1); diff --git a/test_regress/t/t_order_timing.pl b/test_regress/t/t_order_timing.pl new file mode 100755 index 000000000..ee807ce82 --- /dev/null +++ b/test_regress/t/t_order_timing.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 1e-8 / 1e-9; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_order.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timescale 10ns/1ns --timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_order_wireloop.pl b/test_regress/t/t_order_wireloop.pl index ffee37678..41a50e716 100755 --- a/test_regress/t/t_order_wireloop.pl +++ b/test_regress/t/t_order_wireloop.pl @@ -16,7 +16,7 @@ compile( # However we no longer gate optimize this # Can't use expect_filename here as unstable output expect => -'%Warning-UNOPT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Feedback to public clock or circular logic: \'bar\' +'%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'bar\' ', ); diff --git a/test_regress/t/t_package_ddecl.pl b/test_regress/t/t_package_ddecl.pl index b46d46042..f5debdb98 100755 --- a/test_regress/t/t_package_ddecl.pl +++ b/test_regress/t/t_package_ddecl.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ['--no-timing'], ); execute( diff --git a/test_regress/t/t_package_ddecl_timing.pl b/test_regress/t/t_package_ddecl_timing.pl new file mode 100755 index 000000000..d471a7adb --- /dev/null +++ b/test_regress/t/t_package_ddecl_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_package_ddecl.v"); + + compile( + verilator_flags2 => ['--timing'], + timing_loop => 1, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_param_array8.pl b/test_regress/t/t_param_array8.pl new file mode 100755 index 000000000..b46d46042 --- /dev/null +++ b/test_regress/t/t_param_array8.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_param_array8.v b/test_regress/t/t_param_array8.v new file mode 100644 index 000000000..c19d4bba8 --- /dev/null +++ b/test_regress/t/t_param_array8.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module sub + #( + parameter int unsigned VAL[2] = '{1, 2} + ) + (); +endmodule + +module t; + sub sub12 (); + sub #(.VAL ( '{3, 4} )) sub34 (); + + initial begin + if (sub12.VAL[0] != 1) $stop; + if (sub12.VAL[1] != 2) $stop; + if (sub34.VAL[0] != 3) $stop; + if (sub34.VAL[1] != 4) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_parse_delay.pl b/test_regress/t/t_parse_delay.pl index b46d46042..40a477a35 100755 --- a/test_regress/t/t_parse_delay.pl +++ b/test_regress/t/t_parse_delay.pl @@ -11,10 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - ); - -execute( - check_finished => 1, + verilator_flags2 => ['--no-timing'], ); ok(1); diff --git a/test_regress/t/t_parse_delay_timing.pl b/test_regress/t/t_parse_delay_timing.pl new file mode 100755 index 000000000..f4847bf8c --- /dev/null +++ b/test_regress/t/t_parse_delay_timing.pl @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_parse_delay.v"); + + compile( + verilator_flags2 => ['--timing'], + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_past_strobe.out b/test_regress/t/t_past_strobe.out new file mode 100644 index 000000000..1f96befeb --- /dev/null +++ b/test_regress/t/t_past_strobe.out @@ -0,0 +1,10 @@ +1 == 1, 0 == 0 +2 == 2, 1 == 1 +3 == 3, 2 == 2 +4 == 4, 3 == 3 +5 == 5, 4 == 4 +6 == 6, 5 == 5 +7 == 7, 6 == 6 +8 == 8, 7 == 7 +9 == 9, 8 == 8 +*-* All Finished *-* diff --git a/test_regress/t/t_past_strobe.pl b/test_regress/t/t_past_strobe.pl new file mode 100755 index 000000000..4bbe254e3 --- /dev/null +++ b/test_regress/t/t_past_strobe.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_past_strobe.v b/test_regress/t/t_past_strobe.v new file mode 100644 index 000000000..4ccbce987 --- /dev/null +++ b/test_regress/t/t_past_strobe.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg [3:0] a, b; + + Test1 t1(clk, a, b); + + initial begin + a = 0; + b = 0; + end + + always @(posedge clk) begin + a <= a + 1; + b = b + 1; + + if (b >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module Test1( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + always @(posedge clk) begin + if (a < 9) $strobe("%0d == %0d, %0d == %0d", a, b, $past(a), $past(b)); + end + +endmodule diff --git a/test_regress/t/t_pp_defnettype_bad.out b/test_regress/t/t_pp_defnettype_bad.out new file mode 100644 index 000000000..e9d6d411e --- /dev/null +++ b/test_regress/t/t_pp_defnettype_bad.out @@ -0,0 +1,11 @@ +%Error-UNSUPPORTED: t/t_pp_defnettype_bad.v:7:1: Unsupported: `default_nettype of other than none or wire: '`default_nettype bad' + 7 | `default_nettype bad_none_such + | ^~~~~~~~~~~~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_pp_defnettype_bad.v:9:1: Unsupported: Verilog optional directive not implemented: '`default_trireg_strength this_is_optional' + 9 | `default_trireg_strength this_is_optional + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Error: t/t_pp_defnettype_bad.v:7:21: syntax error, unexpected IDENTIFIER + 7 | `default_nettype bad_none_such + | ^~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_pp_defnettype_bad.pl b/test_regress/t/t_pp_defnettype_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_pp_defnettype_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_pp_defnettype_bad.v b/test_regress/t/t_pp_defnettype_bad.v new file mode 100644 index 000000000..fc8aa9c1a --- /dev/null +++ b/test_regress/t/t_pp_defnettype_bad.v @@ -0,0 +1,9 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`default_nettype bad_none_such + +`default_trireg_strength this_is_optional diff --git a/test_regress/t/t_preproc_kwd_bad.out b/test_regress/t/t_preproc_kwd_bad.out new file mode 100644 index 000000000..3fa950ee7 --- /dev/null +++ b/test_regress/t/t_preproc_kwd_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_preproc_kwd_bad.v:8:1: `end_keywords when not inside `begin_keywords block + 8 | `end_keywords + | ^~~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_preproc_kwd_bad.pl b/test_regress/t/t_preproc_kwd_bad.pl new file mode 100755 index 000000000..59ba0d6c6 --- /dev/null +++ b/test_regress/t/t_preproc_kwd_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_preproc_kwd_bad.v b/test_regress/t/t_preproc_kwd_bad.v new file mode 100644 index 000000000..16c423b8b --- /dev/null +++ b/test_regress/t/t_preproc_kwd_bad.v @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`end_keywords +`end_keywords // BAD + +module t; +endmodule diff --git a/test_regress/t/t_preproc_nodef_bad.out b/test_regress/t/t_preproc_nodef_bad.out new file mode 100644 index 000000000..1ee339862 --- /dev/null +++ b/test_regress/t/t_preproc_nodef_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_preproc_nodef_bad.v:7:1: Define or directive not defined: '`not_defined' + 7 | `not_defined + | ^~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_preproc_nodef_bad.pl b/test_regress/t/t_preproc_nodef_bad.pl new file mode 100755 index 000000000..59ba0d6c6 --- /dev/null +++ b/test_regress/t/t_preproc_nodef_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_preproc_nodef_bad.v b/test_regress/t/t_preproc_nodef_bad.v new file mode 100644 index 000000000..3c0c23d5b --- /dev/null +++ b/test_regress/t/t_preproc_nodef_bad.v @@ -0,0 +1,10 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`not_defined + +module t; +endmodule diff --git a/test_regress/t/t_process_bad.out b/test_regress/t/t_process_bad.out index a4ada3778..3a5b96793 100644 --- a/test_regress/t/t_process_bad.out +++ b/test_regress/t/t_process_bad.out @@ -1,10 +1,10 @@ -%Error: t/t_process.v:22:4: Can't find typedef: 'process' - 22 | process p; +%Error: t/t_process_bad.v:8:4: Can't find typedef: 'process' + 8 | process p; | ^~~~~~~ -%Error-UNSUPPORTED: t/t_process.v:26:20: Unsupported: 'process' - 26 | p = process::self(); +%Error-UNSUPPORTED: t/t_process_bad.v:12:20: Unsupported: 'process' + 12 | p = process::self(); | ^~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Internal Error: t/t_process.v:26:11: ../V3LinkDot.cpp:#: Bad package link - 26 | p = process::self(); +%Error: Internal Error: t/t_process_bad.v:12:11: ../V3LinkDot.cpp:#: Bad package link + 12 | p = process::self(); | ^~~~~~~ diff --git a/test_regress/t/t_process_bad.pl b/test_regress/t/t_process_bad.pl index 7be24ae56..dd3bcbbf3 100755 --- a/test_regress/t/t_process_bad.pl +++ b/test_regress/t/t_process_bad.pl @@ -10,8 +10,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -top_filename("t_process.v"); - lint( verilator_flags2 => ["--xml-only"], fails => 1, diff --git a/test_regress/t/t_process_std.out b/test_regress/t/t_process_std.out new file mode 100644 index 000000000..80b26b03c --- /dev/null +++ b/test_regress/t/t_process_std.out @@ -0,0 +1,11 @@ +%Error: t/t_process.pl:1:1: syntax error, unexpected '#' + 1 | #!/usr/bin/env perl + | ^ +%Error-UNSUPPORTED: t/t_process.pl:2:19: Unsupported: Verilog 2001-config reserved word not implemented: 'use' + 2 | if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } + | ^~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_process.pl:3:47: Unsupported: SystemVerilog 2005 reserved word not implemented: 'expect' + 3 | # DESCRIPTION: Verilator: Verilog Test driver/expect definition + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_process_std.pl b/test_regress/t/t_process_std.pl new file mode 100755 index 000000000..462019b41 --- /dev/null +++ b/test_regress/t/t_process_std.pl @@ -0,0 +1,26 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_process.pl"); + +compile( + v_flags2 => ["+define+T_PROCESS+std::process"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_program_anonymous.out b/test_regress/t/t_program_anonymous.out new file mode 100644 index 000000000..d4a02c68a --- /dev/null +++ b/test_regress/t/t_program_anonymous.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_program_anonymous.v:7:1: Unsupported: Anonymous programs + 7 | program; + | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_event_control_unsup.pl b/test_regress/t/t_program_anonymous.pl similarity index 100% rename from test_regress/t/t_event_control_unsup.pl rename to test_regress/t/t_program_anonymous.pl diff --git a/test_regress/t/t_program_anonymous.v b/test_regress/t/t_program_anonymous.v new file mode 100644 index 000000000..5df52f197 --- /dev/null +++ b/test_regress/t/t_program_anonymous.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +program; + task atask; + endtask + function int afunc(input int i); + return i+1; + endfunction +class acls; + static int i = 10; +endclass +endprogram + +program t(/*AUTOARG*/); + + int i; + + initial begin + atask(); + + i = afunc(2); + if (i != 3) $stop; + + if (acls::i != 10) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + +endprogram diff --git a/test_regress/t/t_program_extern.out b/test_regress/t/t_program_extern.out new file mode 100644 index 000000000..de3c1a979 --- /dev/null +++ b/test_regress/t/t_program_extern.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_program_extern.v:7:1: Unsupported: extern program + 7 | extern program pgm; + | ^~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_program_extern.pl b/test_regress/t/t_program_extern.pl new file mode 100755 index 000000000..be66c40e6 --- /dev/null +++ b/test_regress/t/t_program_extern.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_program_extern.v b/test_regress/t/t_program_extern.v new file mode 100644 index 000000000..67bf42028 --- /dev/null +++ b/test_regress/t/t_program_extern.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +extern program pgm; + +program pgm; + task ptask; + endtask +endprogram + +module t(/*AUTOARG*/); + + pgm sub (); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_protect_ids_key.out b/test_regress/t/t_protect_ids_key.out index 45059c7d7..5f362ac43 100644 --- a/test_regress/t/t_protect_ids_key.out +++ b/test_regress/t/t_protect_ids_key.out @@ -11,7 +11,9 @@ - + + + @@ -20,17 +22,27 @@ + + + + + + + + + - + - - - + + + + diff --git a/test_regress/t/t_sampled_expr.pl b/test_regress/t/t_sampled_expr.pl new file mode 100755 index 000000000..c505d6263 --- /dev/null +++ b/test_regress/t/t_sampled_expr.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ['--assert'], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_sampled_expr.v b/test_regress/t/t_sampled_expr.v new file mode 100644 index 000000000..9447b48f8 --- /dev/null +++ b/test_regress/t/t_sampled_expr.v @@ -0,0 +1,68 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg [3:0] a, b; + + Test1 t1(clk, a, b); + Test2 t2(clk, a, b); + Test3 t3(clk); + + initial begin + a = 0; + b = 0; + end + + always @(posedge clk) begin + a <= a + 1; + b = b + 1; + + $display("a = %0d, b = %0d, %0d == %0d", a, b, $sampled(a), $sampled(b)); + + if (b >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module Test1( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) $sampled(a == b) == ($sampled(a) == $sampled(b))); +endmodule + +module Test2( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) eq(a, b)); + + function [0:0] eq([3:0] x, y); + return x == y; + endfunction +endmodule + +module Test3( + clk + ); + + input clk; + + assert property (@(posedge clk) $sampled($time) == $time); +endmodule diff --git a/test_regress/t/t_sampled_expr_unsup.out b/test_regress/t/t_sampled_expr_unsup.out new file mode 100644 index 000000000..8a8a1634d --- /dev/null +++ b/test_regress/t/t_sampled_expr_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_sampled_expr_unsup.v:20:38: Unsupported: Write to variable in sampled expression + : ... In instance t + 20 | assert property (@(posedge clk) f(a) >= 0); + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_sampled_expr_unsup.pl b/test_regress/t/t_sampled_expr_unsup.pl new file mode 100755 index 000000000..24a95330a --- /dev/null +++ b/test_regress/t/t_sampled_expr_unsup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + expect_filename=>$Self->{golden_filename}, + verilator_flags2=> ['--assert -Wno-UNSIGNED'], + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_sampled_expr_unsup.v b/test_regress/t/t_sampled_expr_unsup.v new file mode 100644 index 000000000..b46d74bf1 --- /dev/null +++ b/test_regress/t/t_sampled_expr_unsup.v @@ -0,0 +1,21 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + int a = 0; + + function int f(output int a); + a = 1; + return a; + endfunction + + assert property (@(posedge clk) f(a) >= 0); +endmodule diff --git a/test_regress/t/t_scheduling_0.pl b/test_regress/t/t_scheduling_0.pl new file mode 100755 index 000000000..07078dca2 --- /dev/null +++ b/test_regress/t/t_scheduling_0.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_0.v b/test_regress/t/t_scheduling_0.v new file mode 100644 index 000000000..aa54a8aaa --- /dev/null +++ b/test_regress/t/t_scheduling_0.v @@ -0,0 +1,66 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE $c(1); +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE |($random | $random); +`endif + +module top( + clk +); + + input clk; + + // Generate half speed 'clk_half', via non-blocking assignment + reg clk_half = 0; + always @(posedge clk) + clk_half <= ~clk_half; + + // 'clk_half_also' is the same as 'clk_half'. + wire clk_half_also = clk_half & `IMPURE_ONE; + + // Random data updated by full speed clock + reg q = 0; + always @(posedge clk) + q <= ($random % 2 == 1) ? 1'b1 : 1'b0; + + // Flop `q` via `clk_half` + reg a = 0; + always @(posedge clk_half) + a <= q; + + // Flop `q` via `clk_half_also` + reg b = 0; + always @(posedge clk_half_also) + b <= q; + + // Cycle count + reg [31:0] cyc = 0; + + // `a` should always equal `b`, no mater which value they actually capture + always @(posedge clk) begin + if (a !== b) begin + $display("tick %d: a is %1d, b is %1d (q is %1d)", cyc, a, b, q); + $stop; + end + end + + // Just stop condition + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_scheduling_1.pl b/test_regress/t/t_scheduling_1.pl new file mode 100755 index 000000000..07078dca2 --- /dev/null +++ b/test_regress/t/t_scheduling_1.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_1.v b/test_regress/t/t_scheduling_1.v new file mode 100644 index 000000000..510754caa --- /dev/null +++ b/test_regress/t/t_scheduling_1.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module top( + clk +); + + input clk; + + // Generate half speed 'clk_half', via blocking assignment + reg clk_half = 0; + always @(posedge clk) + clk_half = ~clk_half; + + // Cycle count (+ stop condition) + reg [31:0] cyc = 0; + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // Flop cycle count via `clk` + reg [31:0] a = 0; + always @(posedge clk) + a <= cyc; + + // Flop cycle count via `clk_half`, on both edges + reg [31:0] b = 0; + always @(posedge clk_half or negedge clk_half) + b <= cyc; + + // `a` should always equal `b`, no mater which value they actually capture + always @(posedge clk) begin + if (a !== b) begin + $display("tick %d: a is %x, b is %x", cyc, a, b); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_scheduling_2.pl b/test_regress/t/t_scheduling_2.pl new file mode 100755 index 000000000..07078dca2 --- /dev/null +++ b/test_regress/t/t_scheduling_2.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_2.v b/test_regress/t/t_scheduling_2.v new file mode 100644 index 000000000..9ed55461c --- /dev/null +++ b/test_regress/t/t_scheduling_2.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE $c(1); +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE |($random | $random); +`endif + +module top( + clk +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a, b, c; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (a !== cyc + 1) $stop; + if (b !== cyc + 2) $stop; + if (c !== cyc + 2) $stop; + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(clk) a = cyc + `IMPURE_ONE; + always @(a) b = a + `IMPURE_ONE; + assign c = a + `IMPURE_ONE; + +endmodule diff --git a/test_regress/t/t_scheduling_3.pl b/test_regress/t/t_scheduling_3.pl new file mode 100755 index 000000000..07078dca2 --- /dev/null +++ b/test_regress/t/t_scheduling_3.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_3.v b/test_regress/t/t_scheduling_3.v new file mode 100644 index 000000000..ad12dc8ab --- /dev/null +++ b/test_regress/t/t_scheduling_3.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE $c(1); +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE |($random | $random); +`endif + +module top( + clk +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a, b, c; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (a !== cyc + 1) $stop; + if (b !== cyc + 2) $stop; + if (c !== cyc + 2) $stop; + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(a) b = a + `IMPURE_ONE; + always @(cyc) a = cyc + `IMPURE_ONE; + assign c = a + `IMPURE_ONE; + +endmodule diff --git a/test_regress/t/t_scheduling_4.pl b/test_regress/t/t_scheduling_4.pl new file mode 100755 index 000000000..07078dca2 --- /dev/null +++ b/test_regress/t/t_scheduling_4.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_4.v b/test_regress/t/t_scheduling_4.v new file mode 100644 index 000000000..6b77f6bb9 --- /dev/null +++ b/test_regress/t/t_scheduling_4.v @@ -0,0 +1,49 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE $c(1); +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE |($random | $random); +`endif + +module top( + clk +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a = 1, b = 2, c = 2; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (cyc > 0) begin + if (a !== cyc + 1) $stop; + if (b !== cyc + 2) $stop; + if (c !== cyc + 2) $stop; + end + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(edge cyc[0]) a = cyc + `IMPURE_ONE; + always @(edge a[0]) b = a + `IMPURE_ONE; + assign c = a + `IMPURE_ONE; + +endmodule diff --git a/test_regress/t/t_scheduling_5.pl b/test_regress/t/t_scheduling_5.pl new file mode 100755 index 000000000..c485f7c23 --- /dev/null +++ b/test_regress/t/t_scheduling_5.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["-Wno-MULTIDRIVEN"] + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_5.v b/test_regress/t/t_scheduling_5.v new file mode 100644 index 000000000..6ca049d70 --- /dev/null +++ b/test_regress/t/t_scheduling_5.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg start = 0; + reg [31:0] count; + reg [31:0] runner = 0; + + always @ (posedge start) count = 0; + always @ (posedge start) runner = 3; + + always @ (runner) begin + if (runner > 0) begin + $display("count=%d runner=%d",count, runner); + count = count + 1; + runner = runner - 1;; + end + end + + reg [7:0] cyc = 0; + always @ (posedge clk) begin + cyc <= cyc + 8'd1; + case (cyc) + 8'd00: start <= 1'b0; + 8'd01: start <= 1'b1; + 8'd02: begin + $display("Final count=%d", count); + if (count!=32'h3) $stop; + end + default: begin + $write("*-* All Finished *-*\n"); + $finish; + end + endcase + end +endmodule diff --git a/test_regress/t/t_scheduling_6.v b/test_regress/t/t_scheduling_6.v new file mode 100644 index 000000000..cfb4f52e4 --- /dev/null +++ b/test_regress/t/t_scheduling_6.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2022 by Geza Lore. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module top( + clk +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a, b, c; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (cyc + 1 !== a) $stop; + if (cyc + 2 !== b) $stop; + if (cyc + 2 !== c) $stop; + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(posedge clk) a = cyc + $c(1); + always @(a) b = a + $c(1); + assign c = a + $c(1); + +endmodule diff --git a/test_regress/t/t_semaphore.out b/test_regress/t/t_semaphore.out index 76d60e04d..95ce89973 100644 --- a/test_regress/t/t_semaphore.out +++ b/test_regress/t/t_semaphore.out @@ -1,7 +1,7 @@ -%Error: t/t_semaphore.v:17:4: Can't find typedef: 'semaphore' - 17 | semaphore s; +%Error: t/t_semaphore.v:21:4: Can't find typedef: 'semaphore' + 21 | semaphore s; | ^~~~~~~~~ -%Error: t/t_semaphore.v:18:4: Can't find typedef: 'semaphore' - 18 | semaphore s2; +%Error: t/t_semaphore.v:22:4: Can't find typedef: 'semaphore' + 22 | semaphore s2; | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_semaphore.v b/test_regress/t/t_semaphore.v index bc229ad75..500fac7e8 100644 --- a/test_regress/t/t_semaphore.v +++ b/test_regress/t/t_semaphore.v @@ -12,14 +12,22 @@ // function int try_get(int keyCount = 1); // endclass +`ifndef SEMAPHORE_T + `define SEMAPHORE_T semaphore +`endif + module t(/*AUTOARG*/); // From UVM: - semaphore s; - semaphore s2; + `SEMAPHORE_T s; + `SEMAPHORE_T s2; int msg; initial begin - s = new(4); + s = new(1); + if (s.try_get() == 0) $stop; + if (s.try_get() != 0) $stop; + + s = new; if (s.try_get() != 0) $stop; s.put(); @@ -31,7 +39,6 @@ module t(/*AUTOARG*/); s.put(2); if (s.try_get(2) <= 0) $stop; -`ifndef VERILATOR fork begin #10; // So later then get() starts below @@ -44,7 +51,6 @@ module t(/*AUTOARG*/); s.get(); end join -`endif s2 = new; if (s2.try_get() != 0) $stop; diff --git a/test_regress/t/t_semaphore_bad.out b/test_regress/t/t_semaphore_bad.out index 76d60e04d..c07616459 100644 --- a/test_regress/t/t_semaphore_bad.out +++ b/test_regress/t/t_semaphore_bad.out @@ -1,7 +1,4 @@ -%Error: t/t_semaphore.v:17:4: Can't find typedef: 'semaphore' - 17 | semaphore s; - | ^~~~~~~~~ -%Error: t/t_semaphore.v:18:4: Can't find typedef: 'semaphore' - 18 | semaphore s2; +%Error: t/t_semaphore_bad.v:8:4: Can't find typedef: 'semaphore' + 8 | semaphore s; | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_semaphore_bad.pl b/test_regress/t/t_semaphore_bad.pl index 92483cce2..a083f46f5 100755 --- a/test_regress/t/t_semaphore_bad.pl +++ b/test_regress/t/t_semaphore_bad.pl @@ -10,10 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -top_filename("t_semaphore.v"); - lint( - verilator_flags2 => ["--xml-only"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_semaphore_class.pl b/test_regress/t/t_semaphore_class.pl new file mode 100755 index 000000000..3329d516e --- /dev/null +++ b/test_regress/t/t_semaphore_class.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--timing"], + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_semaphore_class.v b/test_regress/t/t_semaphore_class.v new file mode 100644 index 000000000..9460c6cf4 --- /dev/null +++ b/test_regress/t/t_semaphore_class.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +class semaphore_cls; + // Test an implementation similar to what Verilator will do internally + int m_keys; + function new(int keyCount = 0); + m_keys = keyCount; + endfunction + function void put(int keyCount = 1); + m_keys += keyCount; + endfunction + task get(int keyCount = 1); + wait (m_keys >= keyCount); + m_keys -= keyCount; + endtask + function int try_get(int keyCount = 1); + if (m_keys >= keyCount) begin + m_keys -= keyCount; + return 1; + end + else begin + return 0; + end + endfunction +endclass + +`define SEMAPHORE_T semaphore_cls + +`include "t_semaphore.v" diff --git a/test_regress/t/t_semaphore_std.out b/test_regress/t/t_semaphore_std.out new file mode 100644 index 000000000..884a7159b --- /dev/null +++ b/test_regress/t/t_semaphore_std.out @@ -0,0 +1,11 @@ +%Error: t/t_semaphore.pl:1:1: syntax error, unexpected '#' + 1 | #!/usr/bin/env perl + | ^ +%Error-UNSUPPORTED: t/t_semaphore.pl:2:19: Unsupported: Verilog 2001-config reserved word not implemented: 'use' + 2 | if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } + | ^~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_semaphore.pl:3:47: Unsupported: SystemVerilog 2005 reserved word not implemented: 'expect' + 3 | # DESCRIPTION: Verilator: Verilog Test driver/expect definition + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_semaphore_std.pl b/test_regress/t/t_semaphore_std.pl new file mode 100755 index 000000000..b6c87f81b --- /dev/null +++ b/test_regress/t/t_semaphore_std.pl @@ -0,0 +1,26 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_semaphore.pl"); + +compile( + v_flags2 => ["+define+T_SEMAPHORE+std::semaphore"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_string_type_methods_bad.out b/test_regress/t/t_string_type_methods_bad.out index 69015ba0f..0c3389838 100644 --- a/test_regress/t/t_string_type_methods_bad.out +++ b/test_regress/t/t_string_type_methods_bad.out @@ -10,4 +10,8 @@ : ... In instance t 17 | s.itoa(1,2,3); | ^~~~ +%Error: t/t_string_type_methods_bad.v:18:9: Unknown built-in string method 'bad_no_such_method' + : ... In instance t + 18 | s.bad_no_such_method(); + | ^~~~~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_string_type_methods_bad.v b/test_regress/t/t_string_type_methods_bad.v index 45c845d0b..ca6f819e7 100644 --- a/test_regress/t/t_string_type_methods_bad.v +++ b/test_regress/t/t_string_type_methods_bad.v @@ -15,6 +15,7 @@ module t (/*AUTOARG*/); i = s.len(0); // BAD s.itoa; // BAD s.itoa(1,2,3); // BAD + s.bad_no_such_method(); // BAD end endmodule diff --git a/test_regress/t/t_sys_rand_concat.pl b/test_regress/t/t_sys_rand_concat.pl index 62d68c9f8..7eb1032d5 100755 --- a/test_regress/t/t_sys_rand_concat.pl +++ b/test_regress/t/t_sys_rand_concat.pl @@ -17,7 +17,9 @@ execute( check_finished => 1, ); -file_grep_not(glob_one("$Self->{obj_dir}/Vt_sys_rand_concat___024root__DepSet_*__0__Slow.cpp"), qr/(<<|>>)/x); +for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet*__Slow.cpp")) { + file_grep_not($file, qr/(<<|>>)/x); +} ok(1); 1; diff --git a/test_regress/t/t_time_sc_bad_mt.out b/test_regress/t/t_time_sc_bad_mt.out new file mode 100644 index 000000000..86603162d --- /dev/null +++ b/test_regress/t/t_time_sc_bad_mt.out @@ -0,0 +1,2 @@ +%Error: SystemC's sc_set_time_resolution is 10^-9, which does not match Verilog timeprecision 10^-12. Suggest use 'sc_set_time_resolution(1s)', or Verilator '--timescale-override 1s/1s' +Aborting... diff --git a/test_regress/t/t_time_sc_bad_mt.pl b/test_regress/t/t_time_sc_bad_mt.pl new file mode 100755 index 000000000..6b6f6d637 --- /dev/null +++ b/test_regress/t/t_time_sc_bad_mt.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vltmt => 1); + +top_filename("t/t_time_sc.v"); + +$Self->{sc_time_resolution} = 'SC_NS'; + +compile( + verilator_flags2 => ['-sc', '-timescale 1ps/1ps', # Mismatch w/sc_time_resolution + '+define+TEST_EXPECT=2us'], + threads => 2, + ); + +execute( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); + +1; diff --git a/test_regress/t/t_timing_always.pl b/test_regress/t/t_timing_always.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_always.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_always.v b/test_regress/t/t_timing_always.v new file mode 100644 index 000000000..e92bc2539 --- /dev/null +++ b/test_regress/t/t_timing_always.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + logic clk = 0; + always #3 clk = ~clk; + + logic flag_a; + logic flag_b; + always @(posedge clk) + begin + `WRITE_VERBOSE(("[%0t] b <= 0\n", $time)); + flag_b <= 1'b0; + #2 + `WRITE_VERBOSE(("[%0t] a <= 1\n", $time)); + flag_a <= 1'b1; + #2 + `WRITE_VERBOSE(("[%0t] b <= 1\n", $time)); + flag_b <= 1'b1; + end + always @(flag_a) if ($time > 0) + begin + #1 + `WRITE_VERBOSE(("[%0t] Checking if b == 0\n", $time)); + if (flag_b !== 1'b0) $stop; + #2 + `WRITE_VERBOSE(("[%0t] Checking if b == 1\n", $time)); + if (flag_b !== 1'b1) $stop; + #10 + $write("*-* All Finished *-*\n"); + $finish; + end + initial #20 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_class.pl b/test_regress/t/t_timing_class.pl new file mode 100755 index 000000000..c469d3de3 --- /dev/null +++ b/test_regress/t/t_timing_class.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_class.v b/test_regress/t/t_timing_class.v new file mode 100644 index 000000000..263b2e44f --- /dev/null +++ b/test_regress/t/t_timing_class.v @@ -0,0 +1,237 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + // ============================================= + // EVENTS + class EventClass; + event e; + int trig_count; + + function new; + trig_count = 0; + endfunction + + task inc_trig_count; + trig_count++; + endtask; + + task sleep; + @e inc_trig_count; + `WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time)); + endtask + + task wake; + ->e; + endtask + endclass + + class WaitClass; + int a; + int b; + logic ok; + + function new; + a = 0; + b = 0; + ok = 0; + endfunction + + task await; + wait(a == 4 && b > 16) if (a != 4 || b <= 16) $stop; + ok = 1; + `WRITE_VERBOSE(("Condition in object met at time %0t!\n", $time)); + endtask + endclass + + class LocalWaitClass; + logic ok; + + function new; + ok = 0; + endfunction + + task await; + int a = 0; + int b = 100; + fork + wait(a == 42 || b != 100) if (a != 42 && b == 100) $stop; + #10 a = 42; + join + ok = 1; + `WRITE_VERBOSE(("Condition with local variables met at time %0t!\n", $time)); + endtask + endclass + + EventClass ec = new; + WaitClass wc = new; + LocalWaitClass lc = new; + + initial begin + @ec.e; + ec.sleep; + if (wc.ok) $stop; + wc.await; + if (lc.ok) $stop; + lc.await; + end + + initial #20 ec.wake; + initial #40 ->ec.e; + initial begin + wc.a = #50 4; + wc.b = #10 32; + end + + always @ec.e begin + ec.inc_trig_count; + `WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time)); + end + + initial begin + #80 + if (ec.trig_count != 3) $stop; + if (!wc.ok) $stop; + if (!lc.ok) $stop; + end + + // ============================================= + // DELAYS + virtual class DelayClass; + pure virtual task do_delay; + pure virtual task do_sth_else; + endclass + + `ifdef TEST_VERBOSE + `define DELAY_CLASS(dt) \ + class Delay``dt extends DelayClass; \ + virtual task do_delay; \ + $write("Starting a #%0d delay\n", dt); \ + #dt \ + $write("Ended a #%0d delay\n", dt); \ + endtask \ + virtual task do_sth_else; \ + $write("Task with no delay (in Delay%0d)\n", dt); \ + endtask \ + endclass + `else + `define DELAY_CLASS(dt) \ + class Delay``dt extends DelayClass; \ + virtual task do_delay; \ + #dt; \ + endtask \ + virtual task do_sth_else; \ + endtask \ + endclass + `endif + + `DELAY_CLASS(10); + `DELAY_CLASS(20); + `DELAY_CLASS(40); + + class NoDelay extends DelayClass; + virtual task do_delay; + `WRITE_VERBOSE(("Task with no delay\n")); + endtask + virtual task do_sth_else; + `WRITE_VERBOSE(("Task with no delay (in NoDelay)\n")); + endtask + endclass + + class AssignDelayClass; + logic x; + logic y; + task do_assign; + y = #10 x; + `WRITE_VERBOSE(("Did assignment with delay\n")); + endtask + endclass + + initial begin + DelayClass dc; + Delay10 d10 = new; + Delay20 d20 = new; + Delay40 d40 = new; + NoDelay dNo = new; + AssignDelayClass dAsgn = new; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + dc = d10; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + if ($time != 10) $stop; + dc = d20; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + if ($time != 30) $stop; + dc = d40; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + if ($time != 70) $stop; + dc = dNo; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + dAsgn.x = 1; + dAsgn.y = 0; + fork #5 dAsgn.x = 0; join_none + dAsgn.do_assign; + if ($time != 80) $stop; + if (dAsgn.y != 1) $stop; + // Test if the object is deleted before do_assign finishes: + fork dAsgn.do_assign; join_none + #5 dAsgn = null; + #15 $write("*-* All Finished *-*\n"); + $finish; + end + + // ============================================= + // FORKS + class ForkDelayClass; + task do_delay; #40; endtask + endclass + + class ForkClass; + int done = 0; + task do_fork(); + ForkDelayClass d; + fork + begin + #10 done++; + `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time)); + end + begin + #20 done++; + `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time)); + d = new; + end + begin + #30 d.do_delay; + done++; + `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time)); + end + join + done++; + `WRITE_VERBOSE(("All forked processes ended at time %0t\n", $time)); + endtask + endclass + + initial begin + ForkClass fc = new; + fc.do_fork; + if (fc.done != 4 || $time != 70) $stop; + end + + initial #101 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_clkgen1.pl b/test_regress/t/t_timing_clkgen1.pl index e1998178c..aa7288ef4 100755 --- a/test_regress/t/t_timing_clkgen1.pl +++ b/test_regress/t/t_timing_clkgen1.pl @@ -2,7 +2,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2019 by Wilson Snyder. This program is free software; you +# Copyright 2022 by Antmicro Ltd. 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. @@ -10,20 +10,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -$Self->{vlt_all} and unsupported("Verilator unsupported, clocking"); +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing -Wno-MINTYPMAXDLY"], + make_main => 0, + ); -compile( - #verilator_flags2 => ['--exe --build --main --timing'], # Unsupported - verilator_flags2 => ['--exe --build --main --bbox-unsup -Wno-STMTDLY -Wno-INITIALDLY'], - verilator_make_cmake => 0, - verilator_make_gmake => 0, - make_main => 0, - make_top => 1, - ); - -execute( - check_finished => 1, - ); + execute( + check_finished => 1, + ); +} ok(1); 1; diff --git a/test_regress/t/t_timing_clkgen2.pl b/test_regress/t/t_timing_clkgen2.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_clkgen2.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_clkgen2.v b/test_regress/t/t_timing_clkgen2.v new file mode 100644 index 000000000..c1ec90fb1 --- /dev/null +++ b/test_regress/t/t_timing_clkgen2.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + logic clk = 0; + logic clk_inv; + int cnt1 = 0; + int cnt2 = 0; + + always #4 clk = ~clk; + always @(negedge clk) begin + cnt1++; + `WRITE_VERBOSE(("[%0t] NEG clk (%b)\n", $time, clk)); + end + always @(posedge clk) begin + cnt1++; + `WRITE_VERBOSE(("[%0t] POS clk (%b)\n", $time, clk)); + end + + assign #2 clk_inv = ~clk; + initial forever begin + @(posedge clk_inv) cnt2++; + `WRITE_VERBOSE(("[%0t] POS clk_inv (%b)\n", $time, clk_inv)); + @(negedge clk_inv) cnt2++; + `WRITE_VERBOSE(("[%0t] NEG clk_inv (%b)\n", $time, clk_inv)); + end + + initial #41 begin + if (cnt1 != 10 && cnt2 != 10) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_clkgen3.pl b/test_regress/t/t_timing_clkgen3.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_clkgen3.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_clkgen3.v b/test_regress/t/t_timing_clkgen3.v new file mode 100644 index 000000000..28da638a3 --- /dev/null +++ b/test_regress/t/t_timing_clkgen3.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`timescale 10ns / 1ns + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + logic clk = 0; + logic clk_copy; + int cyc = 0; + int cnt1 = 0; + int cnt2 = 0; + + initial forever #1 clk = ~clk; + + always @(negedge clk) begin + #0.75 cnt1++; + `WRITE_VERBOSE(("[%0t] NEG clk (%b)\n", $time, clk)); + end + + always @(posedge clk) begin + cyc <= cyc + 1; + #0.5 `WRITE_VERBOSE(("[%0t] POS clk (%b)\n", $time, clk)); + if (cyc == 5) begin + if (cnt1 != 4 && cnt2 != 9) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + + assign clk_copy = clk; + always @(posedge clk_copy or negedge clk_copy) begin + #0.25 cnt2++; + `WRITE_VERBOSE(("[%0t] POS/NEG clk_copy (%b)\n", $time, clk_copy)); + end + + initial #100 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_clkgen_sc.pl b/test_regress/t/t_timing_clkgen_sc.pl new file mode 100755 index 000000000..a5fe43850 --- /dev/null +++ b/test_regress/t/t_timing_clkgen_sc.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +elsif (!$Self->have_sc) { + skip("No SystemC installed"); +} +else { + top_filename("t/t_timing_clkgen2.v"); + + compile( + verilator_flags2 => ["--sc --exe --timing --timescale 10ps/1ps"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_clkgen_unsup.out b/test_regress/t/t_timing_clkgen_unsup.out new file mode 100644 index 000000000..8ee56209f --- /dev/null +++ b/test_regress/t/t_timing_clkgen_unsup.out @@ -0,0 +1,6 @@ +%Warning-MINTYPMAXDLY: t/t_timing_clkgen1.v:9:13: Unsupported: minimum/typical/maximum delay expressions. Using the typical delay + 9 | #(8.0:5:3) clk = 1; + | ^ + ... For warning description see https://verilator.org/warn/MINTYPMAXDLY?v=latest + ... Use "/* verilator lint_off MINTYPMAXDLY */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_timing_clkgen_unsup.pl b/test_regress/t/t_timing_clkgen_unsup.pl new file mode 100755 index 000000000..b380d0484 --- /dev/null +++ b/test_regress/t/t_timing_clkgen_unsup.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +top_filename("t/t_timing_clkgen1.v"); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_cmake.pl b/test_regress/t/t_timing_cmake.pl new file mode 100755 index 000000000..acd0f0d36 --- /dev/null +++ b/test_regress/t/t_timing_cmake.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} elsif (!$Self->have_cmake) { + skip("cmake is not installed"); +} else { + top_filename("t/t_timing_events.v"); + + compile( + verilator_flags2 => ["--timescale 10ns/1ns --main --timing"], + verilator_make_gmake => 0, + verilator_make_cmake => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_debug1.out b/test_regress/t/t_timing_debug1.out new file mode 100644 index 000000000..792642e49 --- /dev/null +++ b/test_regress/t/t_timing_debug1.out @@ -0,0 +1,1901 @@ +-V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. +-V{t#,#}+ Vt_timing_debug1___024root___ctor_var_reset +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Initial +-V{t#,#}+ Vt_timing_debug1___024root___eval_static +-V{t#,#}+ Vt_timing_debug1___024root___eval_static__TOP +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__0 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__1 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__2 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__3 +-V{t#,#}+ Vt_timing_debug1___024root___eval_settle +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__stl +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__stl +-V{t#,#} 'stl' region trigger index 0 is active: Internal 'stl' trigger - first iteration +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'stl' region trigger index 2 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#} 'stl' region trigger index 3 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___eval_stl +-V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___stl_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___stl_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__stl +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__stl +-V{t#,#} No triggers active +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 3: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 3: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 6: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 7: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 7: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 9: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 9: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 15: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 15: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 18: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 19: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 19: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 21: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 21: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 24: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 24: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 27: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 27: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 30: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 31: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 31: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 34: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 36: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 36: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 39: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 39: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 42: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 43: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 43: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 48: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 49: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 49: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 51: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 51: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 54: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 56: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 60: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 61: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 61: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 63: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 63: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 67: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 67: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 69: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 69: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 72: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 73: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 73: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 79: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 79: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 88: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +*-* All Finished *-* +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_final diff --git a/test_regress/t/t_timing_debug1.pl b/test_regress/t/t_timing_debug1.pl new file mode 100755 index 000000000..0d4523725 --- /dev/null +++ b/test_regress/t/t_timing_debug1.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_timing_sched.v"); + + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + all_run_flags => ["+verilator+debug"], + check_finished => 1, + ); + + if (!$Self->{vltmt}) { # vltmt output may vary between thread exec order + files_identical("$Self->{obj_dir}/vlt_sim.log", $Self->{golden_filename}, "logfile"); + } +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out new file mode 100644 index 000000000..6243cc26c --- /dev/null +++ b/test_regress/t/t_timing_debug2.out @@ -0,0 +1,620 @@ +-V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. +-V{t#,#}+ Vt_timing_debug2___024root___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay__Vclpkg___ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass__Vclpkg___ctor_var_reset +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Initial +-V{t#,#}+ Vt_timing_debug2___024root___eval_static +-V{t#,#}+ Vt_timing_debug2_t___eval_static__TOP__t +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2___024root___eval_initial +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__0 +-V{t#,#} Suspending process waiting for @([event] t.ec.e) at t/t_timing_class.v:80 +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__1 +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__2 +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__3 +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__4 +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__5 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::__VnoInFunc_do_delay +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__6 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::_ctor_var_reset +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::__VnoInFunc_do_fork +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::__Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::__Vfork_h########__0__1 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::__Vfork_h########__0__2 +-V{t#,#} Awaiting join of fork at: t/t_timing_class.v:209 +-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__7 +-V{t#,#}+ Vt_timing_debug2___024root___eval_settle +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#} Committing processes waiting for @([event] t.ec.e): +-V{t#,#} - Process waiting at t/t_timing_class.v:80 +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 10: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 20: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 10: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 30: Process waiting at t/t_timing_class.v:101 +-V{t#,#} Awaiting time 40: Process waiting at t/t_timing_class.v:137 +-V{t#,#} Awaiting time 50: Process waiting at t/t_timing_class.v:211 +-V{t#,#} Awaiting time 20: Process waiting at t/t_timing_class.v:215 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:220 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:236 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:236 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::__VnoInFunc_do_sth_else +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20::__VnoInFunc_do_delay +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:138 +-V{t#,#} Process forked at t/t_timing_class.v:210 finished +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 20: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 20: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 30: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 30: Process waiting at t/t_timing_class.v:101 +-V{t#,#} Awaiting time 40: Process waiting at t/t_timing_class.v:137 +-V{t#,#} Awaiting time 50: Process waiting at t/t_timing_class.v:211 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:215 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:220 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:220 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass::new +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass::_ctor_var_reset +-V{t#,#} Process forked at t/t_timing_class.v:214 finished +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:215 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_wake +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([event] t.ec.e) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.ec.e): +-V{t#,#} - Process waiting at t/t_timing_class.v:80 +-V{t#,#} Resuming processes waiting for @([event] t.ec.e) +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:80 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_sleep +-V{t#,#} Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#} Doing post updates for processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2_t___nba_sequent__TOP__t__0 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_inc_trig_count +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#} Doing post updates for processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} Doing post updates for processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 30: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 30: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 50: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:101 +-V{t#,#} Awaiting time 40: Process waiting at t/t_timing_class.v:137 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:211 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:211 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20::__VnoInFunc_do_sth_else +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::__VnoInFunc_do_delay +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:139 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass::__VnoInFunc_do_delay +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#} Doing post updates for processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#} Doing post updates for processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} Doing post updates for processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 40: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 50: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:101 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:137 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:202 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:202 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([event] t.ec.e) +-V{t#,#} Doing post updates for processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#} Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting resumption +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} No ready processes waiting for @([event] t.ec.e) +-V{t#,#} Resuming processes waiting for @([event] t.ec.e) +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Resuming processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:29 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:29 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_inc_trig_count +-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass::__VnoInFunc_await +-V{t#,#} Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#} Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2_t___nba_sequent__TOP__t__0 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_inc_trig_count +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#} Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#} Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 50: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:101 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:137 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:137 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#} Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#} Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#} Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 60: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:101 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:92 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:92 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#} Process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 awaiting resumption +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Resuming processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:50 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::__VnoInFunc_await +-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::__Vfork_h########__0__0 +-V{t#,#} Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::__Vfork_h########__0__1 +-V{t#,#} Awaiting join of fork at: t/t_timing_class.v:66 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:67 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:67 +-V{t#,#} Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:67 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:67 +-V{t#,#} Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:67 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:67 +-V{t#,#} Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67 +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:101 +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_class.v:68 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:68 +-V{t#,#} Process forked at t/t_timing_class.v:219 finished +-V{t#,#} Resuming: Process waiting at (null):0 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:101 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::__VnoInFunc_do_sth_else +-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_delay +-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_sth_else +-V{t#,#}+ Vt_timing_debug2_t____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:154 +-V{t#,#} Process forked at t/t_timing_class.v:68 finished +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} Suspended processes waiting for dynamic trigger evaluation: +-V{t#,#} - Process waiting at t/t_timing_class.v:67 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:67 +-V{t#,#} Process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67 awaiting resumption +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Resuming processes: +-V{t#,#} - Process waiting at t/t_timing_class.v:67 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:67 +-V{t#,#} Process forked at t/t_timing_class.v:67 finished +-V{t#,#} Resuming: Process waiting at (null):0 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::~ +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:188 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:188 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 80: Process waiting at t/t_timing_class.v:89 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:91 +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:89 +-V{t#,#}+ Vt_timing_debug2_t____Vfork_h########__0__1 +-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 85: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:154 +-V{t#,#} Awaiting time 90: Process waiting at t/t_timing_class.v:194 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:194 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 90: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:154 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_class.v:195 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:195 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::~ +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_class.v:88 +-V{t#,#} Awaiting time 101: Process waiting at t/t_timing_class.v:154 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:154 +*-* All Finished *-* +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#} No suspended processes waiting for dynamic trigger evaluation +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_final +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass::~ +-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::~ diff --git a/test_regress/t/t_timing_debug2.pl b/test_regress/t/t_timing_debug2.pl new file mode 100755 index 000000000..56ef19876 --- /dev/null +++ b/test_regress/t/t_timing_debug2.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_timing_class.v"); + + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + all_run_flags => ["+verilator+debug"], + check_finished => 1, + ); + + if (!$Self->{vltmt}) { # vltmt output may vary between thread exec order + files_identical("$Self->{obj_dir}/vlt_sim.log", $Self->{golden_filename}, "logfile"); + } +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_delay_callstack.pl b/test_regress/t/t_timing_delay_callstack.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_delay_callstack.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_delay_callstack.v b/test_regress/t/t_timing_delay_callstack.v new file mode 100644 index 000000000..995f25d87 --- /dev/null +++ b/test_regress/t/t_timing_delay_callstack.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + int counter = 0; + + // As Verilator doesn't support recursive calls, let's use macros to + // generate tasks for a deep call stack + `ifdef TEST_VERBOSE + `define DEEP_STACK_DELAY_END(i) \ + task delay``i; \ + counter++; \ + $write("[%0t] at depth %0d\n", $time, i); \ + counter++; \ + endtask + + `define DEEP_STACK_DELAY(i, j) \ + task delay``i; \ + $write("[%0t] entering depth %0d\n", $time, i); \ + #1 delay``j; \ + counter++; \ + #1 $write("[%0t] leaving depth %0d\n", $time, i); \ + counter++; \ + endtask + `else + `define DEEP_STACK_DELAY_END(i) \ + task delay``i; \ + counter += 2; \ + endtask + + `define DEEP_STACK_DELAY(i, j) \ + task delay``i; \ + #1 delay``j; \ + counter++; \ + #1; \ + counter++; \ + endtask + `endif + + `DEEP_STACK_DELAY_END(10); + `DEEP_STACK_DELAY(9, 10); + `DEEP_STACK_DELAY(8, 9); + `DEEP_STACK_DELAY(7, 8); + `DEEP_STACK_DELAY(6, 7); + `DEEP_STACK_DELAY(5, 6); + `DEEP_STACK_DELAY(4, 5); + `DEEP_STACK_DELAY(3, 4); + `DEEP_STACK_DELAY(2, 3); + `DEEP_STACK_DELAY(1, 2); + + initial begin + delay1; + if ($time != 9*2) $stop; + if (counter != 10*2) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_events.pl b/test_regress/t/t_timing_events.pl new file mode 100755 index 000000000..c469d3de3 --- /dev/null +++ b/test_regress/t/t_timing_events.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_events.v b/test_regress/t/t_timing_events.v new file mode 100644 index 000000000..24065256b --- /dev/null +++ b/test_regress/t/t_timing_events.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e1; + event e2; + event e3; + initial forever begin + #2 + ->e1; + #2 + ->e2; + #2 + ->e3; + end + initial begin + for (int i = 0; i < 10; i++) begin + @(e1, e2, e3) + if (!e1.triggered && !e2.triggered && !e3.triggered) $stop; +`ifdef TEST_VERBOSE + $write("got event %0d\n", i); +`endif + end + $write("*-* All Finished *-*\n"); + $finish; + end + + initial #21 $stop; // timeout +endmodule + +`ifndef VERILATOR_TIMING +`error "VERILATOR_TIMING should have been defined as have --timing" +`endif diff --git a/test_regress/t/t_timing_fork_comb.pl b/test_regress/t/t_timing_fork_comb.pl new file mode 100755 index 000000000..d96687ccb --- /dev/null +++ b/test_regress/t/t_timing_fork_comb.pl @@ -0,0 +1,36 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + # Should convert the first always into combo and detect cycle + compile( + fails => 1, + verilator_flags2 => ["--timing"], + expect => + '%Warning-UNOPTFLAT: t/t_timing_fork_comb.v:\d+:\d+: Signal unoptimizable: Circular combinational logic:' + ); + + compile( + verilator_flags2 => ["--exe --main --timing -Wno-UNOPTFLAT"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_comb.v b/test_regress/t/t_timing_fork_comb.v new file mode 100644 index 000000000..8c7daec56 --- /dev/null +++ b/test_regress/t/t_timing_fork_comb.v @@ -0,0 +1,58 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk = 0; + + assign #5 clk = ~clk; + + int a = 0; + always @(posedge clk) begin + a <= a + 1; +`ifdef TEST_VERBOSE + $display("a=%0d, b=%0d, c=%0d, d=%0d, e=%0d, f=%0d, v=%b", a, b, c, d, e, f, v); +`endif + end + + int b = 0, c = 0, d = 0, e = 0, f = 0; + always @a begin + b = a << 1; + fork + #10 d = b + c; + e = c + d; + #5 f = d + e; + join_none + c = a + b; + end + + logic[5:0] v; + always @a begin + v[0] = a[0]; + fork + begin + v[1] = a[1]; + #5 v[2] = a[2]; + end + #10 v[3] = a[3]; + join_none + v[4] = a[4]; + end + + initial #100 begin +`ifdef TEST_VERBOSE + $display("a=%0d, b=%0d, c=%0d, d=%0d, e=%0d, f=%0d, v=%b", a, b, c, d, e, f, v); +`endif + if (a != 10) $stop; + if (b != 20) $stop; + if (c != 30) $stop; + if (d != 45) $stop; + if (e != 75) $stop; + if (f != 107) $stop; + if (v != 'b001010) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_fork_join.out b/test_regress/t/t_timing_fork_join.out new file mode 100644 index 000000000..46f216b1e --- /dev/null +++ b/test_regress/t/t_timing_fork_join.out @@ -0,0 +1,25 @@ +[0] fork..join process 4 +[2] fork..join process 3 +[4] fork..join process 2 +[8] fork..join process 1 +[16] fork in fork starts +[16] fork..join process 8 +[20] fork..join process 7 +[24] fork..join process 6 +[32] fork..join process 5 +[32] fork..join in fork ends +[64] main process +fork..join_any process 2 +back in main process +fork..join_any process 1 +fork..join_any process 1 +back in main process +fork..join_any process 2 +in main process +fork..join_none process 1 +fork..join_none process 2 +fork..join_none process 3 +fork..join_none process 2 again +fork..join_none process 1 again +fork..join_none process 3 again +*-* All Finished *-* diff --git a/test_regress/t/t_timing_fork_join.pl b/test_regress/t/t_timing_fork_join.pl new file mode 100755 index 000000000..4ab3c9652 --- /dev/null +++ b/test_regress/t/t_timing_fork_join.pl @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_join.v b/test_regress/t/t_timing_fork_join.v new file mode 100644 index 000000000..71865da6a --- /dev/null +++ b/test_regress/t/t_timing_fork_join.v @@ -0,0 +1,84 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event event1; + event event2; + event event3; + + initial begin + fork + begin /*empty*/ end + #8 $write("[%0t] fork..join process 1\n", $time); + #4 $write("[%0t] fork..join process 2\n", $time); + #2 $write("[%0t] fork..join process 3\n", $time); + $write("[%0t] fork..join process 4\n", $time); + begin : fork_in_fork #16 + $write("[%0t] fork in fork starts\n", $time); + fork + #16 $write("[%0t] fork..join process 5\n", $time); + #8 $write("[%0t] fork..join process 6\n", $time); + #4 $write("[%0t] fork..join process 7\n", $time); + $write("[%0t] fork..join process 8\n", $time); + join + $write("[%0t] fork..join in fork ends\n", $time); + end + join + #32 $write("[%0t] main process\n", $time); + fork + begin + @event1; + $write("fork..join_any process 1\n"); + ->event1; + end + $write("fork..join_any process 2\n"); + join_any + $write("back in main process\n"); + #1 ->event1; + #1 fork + #2 $write("fork..join_any process 1\n"); + begin + @event1; + $write("fork..join_any process 2\n"); + ->event1; + end + join_any + $write("back in main process\n"); + #1 ->event1; + @event1; + // Order of triggering: + // p1->event2 ==> p2->event3 ==> p3->event3 ==> p2->event2 ==> p1->event3 ==> p3->event1 + fork + begin + #1 $write("fork..join_none process 1\n"); + ->event2; + @event2 $write("fork..join_none process 1 again\n"); + #1 ->event3; + end + begin + @event2 $write("fork..join_none process 2\n"); + #1 ->event3; + @event3 $write("fork..join_none process 2 again\n"); + #1 ->event2; + end + begin + @event3 $write("fork..join_none process 3\n"); + #1 ->event3; + @event3 $write("fork..join_none process 3 again\n"); + ->event1; + end + join_none + $write("in main process\n"); + @event1; + $write("*-* All Finished *-*\n"); + $finish; + end + initial #100 $stop; // timeout + + // Test optimized-out fork statements: + reg a; + initial fork a = 1; join +endmodule diff --git a/test_regress/t/t_timing_fork_many.pl b/test_regress/t/t_timing_fork_many.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_fork_many.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_many.v b/test_regress/t/t_timing_fork_many.v new file mode 100644 index 000000000..555865436 --- /dev/null +++ b/test_regress/t/t_timing_fork_many.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + int counter = 0; + + // As Verilator doesn't support recursive calls, let's use macros to generate tasks + `define FORK2_END(i) \ + task fork2_``i; \ + #1 counter++; \ + endtask + + `define FORK2(i, j) \ + task fork2_``i; \ + fork \ + #1 fork2_``j; \ + #1 fork2_``j; \ + join \ + endtask + + `FORK2_END(0); + `FORK2(1, 0); + `FORK2(2, 1); + `FORK2(3, 2); + `FORK2(4, 3); + `FORK2(5, 4); + `FORK2(6, 5); + `FORK2(7, 6); + `FORK2(8, 7); + + initial begin + fork2_8; +`ifdef TEST_VERBOSE + $write("[%0t] process counter == %0d\n", $time, counter); +`endif + if (counter != 1 << 8) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_fork_unsup.out b/test_regress/t/t_timing_fork_unsup.out new file mode 100644 index 000000000..39713d51f --- /dev/null +++ b/test_regress/t/t_timing_fork_unsup.out @@ -0,0 +1,34 @@ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:12:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 12 | #6 x = 4; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:13:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 13 | #2 x++; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:14:16: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 14 | x = #4 x * 3; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:14:9: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 14 | x = #4 x * 3; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:16:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 16 | #1 if (x != 1) $stop; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:17:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 17 | #2 if (x != 2) $stop; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:18:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 18 | #2 if (x != 3) $stop; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:19:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 19 | #2 if (x != 4) $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_fork_unsup.pl b/test_regress/t/t_timing_fork_unsup.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_timing_fork_unsup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_unsup.v b/test_regress/t/t_timing_fork_unsup.v new file mode 100644 index 000000000..39c141452 --- /dev/null +++ b/test_regress/t/t_timing_fork_unsup.v @@ -0,0 +1,31 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + class C; + task f; + int x = 0; + fork + #6 x = 4; + #2 x++; + x = #4 x * 3; + begin + #1 if (x != 1) $stop; + #2 if (x != 2) $stop; + #2 if (x != 3) $stop; + #2 if (x != 4) $stop; + $finish; + end + join_none + x = 1; + endtask + endclass + + initial begin + C o = new; + o.f; + end +endmodule diff --git a/test_regress/t/t_timing_func_bad.out b/test_regress/t/t_timing_func_bad.out new file mode 100644 index 000000000..c3fb3c88e --- /dev/null +++ b/test_regress/t/t_timing_func_bad.out @@ -0,0 +1,25 @@ +%Error: t/t_timing_func_bad.v:10:7: Delays are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 10 | #1 $stop; + | ^ +%Error: t/t_timing_func_bad.v:15:12: Timing controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 15 | f2 = #5 0; $stop; + | ^ +%Error: t/t_timing_func_bad.v:20:7: Event controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 20 | @e $stop; + | ^ +%Error: t/t_timing_func_bad.v:25:12: Timing controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 25 | f4 = @e 0; $stop; + | ^ +%Error: t/t_timing_func_bad.v:31:7: Wait statements are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 31 | wait(i == 0) $stop; + | ^~~~ +%Error: t/t_timing_func_bad.v:42:7: Delays are not legal in final blocks (IEEE 1800-2017 9.2.3) + : ... In instance t + 42 | #1; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_func_bad.pl b/test_regress/t/t_timing_func_bad.pl new file mode 100755 index 000000000..0ee17366a --- /dev/null +++ b/test_regress/t/t_timing_func_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + verilator_flags2 => ['--no-timing'], + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_func_bad.v b/test_regress/t/t_timing_func_bad.v new file mode 100644 index 000000000..0e8f13b78 --- /dev/null +++ b/test_regress/t/t_timing_func_bad.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/); + + function int f1; + #1 $stop; + f1 = 0; + endfunction + + function int f2; + f2 = #5 0; $stop; + endfunction + + event e; + function int f3; + @e $stop; + f3 = 0; + endfunction + + function int f4; + f4 = @e 0; $stop; + endfunction + + int i; + + function int f5; + wait(i == 0) $stop; + f5 = 0; + endfunction + + initial begin + i = f1(); + $write("*-* All Finished *-*\n"); + $finish; + end + + final begin + #1; + $stop; + end + +endmodule diff --git a/test_regress/t/t_timing_intra_assign.out b/test_regress/t/t_timing_intra_assign.out new file mode 100644 index 000000000..fc870d925 --- /dev/null +++ b/test_regress/t/t_timing_intra_assign.out @@ -0,0 +1,15 @@ +val[0]=0 val[1]=0 val[2]=0 +val[0]=1 val[1]=0 val[2]=0 +val[0]=2 val[1]=0 val[2]=15 +val[0]=3 val[1]=0 val[2]=15 +val[0]=4 val[1]=1 val[2]=14 +val[0]=5 val[1]=1 val[2]=14 +val[0]=6 val[1]=2 val[2]=13 +val[0]=7 val[1]=4 val[2]=11 +val[0]=8 val[1]=7 val[2]=8 +val[0]=9 val[1]=7 val[2]=8 +val[0]=10 val[1]=7 val[2]=8 +val[0]=11 val[1]=7 val[2]=8 +val[0]=12 val[1]=7 val[2]=8 +val[0]=13 val[1]=7 val[2]=8 +*-* All Finished *-* diff --git a/test_regress/t/t_timing_intra_assign.pl b/test_regress/t/t_timing_intra_assign.pl new file mode 100755 index 000000000..6852f73f7 --- /dev/null +++ b/test_regress/t/t_timing_intra_assign.pl @@ -0,0 +1,40 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing -Wno-UNOPTFLAT"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + + compile( + verilator_flags2 => ["--exe --main --timing -Wno-UNOPTFLAT -fno-localize"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_intra_assign.v b/test_regress/t/t_timing_intra_assign.v new file mode 100644 index 000000000..35afe20a9 --- /dev/null +++ b/test_regress/t/t_timing_intra_assign.v @@ -0,0 +1,56 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic[3:0] val[3]; + logic[1:0] idx1 = 0; + logic[1:0] idx2 = 0; + logic[0:0] idx3 = 0; + event e; + + always @val[0] $write("val[0]=%0d val[1]=%0d val[2]=%0d\n", val[0], val[1], val[2]); + + assign #10 {val[1], val[2]} = {val[0], 4'hf-val[0]}; + + always #10 begin // always so we can use NBA + val[0] = 1; + #10 val[0] = 2; + fork #5 val[0] = 3; join_none + val[0] = #10 val[0] + 2; + val[idx1] <= #10 val[idx1] + 2; + fork begin #5 + val[0] = 5; + idx1 = 2; + idx2 = 3; + idx3 = 1; + #40 ->e; + end join_none + val[idx1][idx2[idx3+:2]] = #20 1; + @e val[0] = 8; + fork begin + #1 val[0] = 9; + #2 ->e; + end join_none + val[0] = @e val[0] + 2; + val[0] <= @e val[0] + 2; + fork begin + #1 val[0] = 11; + end join_none + #2 ->e; + idx1 = 0; + idx2 = 0; + idx3 = 0; + fork begin #2 + idx1 = 2; + idx2 = 3; + idx3 = 1; + end join_none + #1 val[idx1[idx3+:2]][idx2] <= @e 1; + #1 ->e; + #1 $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_intra_assign_delay.out b/test_regress/t/t_timing_intra_assign_delay.out deleted file mode 100644 index f726ad4b4..000000000 --- a/test_regress/t/t_timing_intra_assign_delay.out +++ /dev/null @@ -1,31 +0,0 @@ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_delay.v:12:11: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 12 | assign #10 val2 = val1; - | ^~ - ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-STMTDLY: t/t_timing_intra_assign_delay.v:16:6: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 16 | #10 val1 = 2; - | ^~ -%Error-UNSUPPORTED: t/t_timing_intra_assign_delay.v:17:5: Unsupported: fork statements - : ... In instance t - 17 | fork #5 val1 = 3; join_none - | ^~~~ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_delay.v:18:13: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 18 | val1 = #10 val1 + 2; - | ^~ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_delay.v:19:14: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 19 | val1 <= #10 val1 + 2; - | ^~ -%Error-UNSUPPORTED: t/t_timing_intra_assign_delay.v:20:5: Unsupported: fork statements - : ... In instance t - 20 | fork #5 val1 = 5; join_none - | ^~~~ -%Warning-STMTDLY: t/t_timing_intra_assign_delay.v:21:6: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 21 | #20 $write("*-* All Finished *-*\n"); - | ^~ -%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_delay.v b/test_regress/t/t_timing_intra_assign_delay.v deleted file mode 100644 index 4ddc2461d..000000000 --- a/test_regress/t/t_timing_intra_assign_delay.v +++ /dev/null @@ -1,24 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2022 by Antmicro Ltd. -// SPDX-License-Identifier: CC0-1.0 - -module t; - int val1, val2; - - always @val1 $write("[%0t] val1=%0d val2=%0d\n", $time, val1, val2); - - assign #10 val2 = val1; - - initial begin - val1 = 1; - #10 val1 = 2; - fork #5 val1 = 3; join_none - val1 = #10 val1 + 2; - val1 <= #10 val1 + 2; - fork #5 val1 = 5; join_none - #20 $write("*-* All Finished *-*\n"); - $finish; - end -endmodule diff --git a/test_regress/t/t_timing_intra_assign_event.out b/test_regress/t/t_timing_intra_assign_event.out deleted file mode 100644 index 3fd9349f3..000000000 --- a/test_regress/t/t_timing_intra_assign_event.out +++ /dev/null @@ -1,32 +0,0 @@ -%Error-UNSUPPORTED: t/t_timing_intra_assign_event.v:15:5: Unsupported: event control statement in this location - : ... In instance t - : ... Suggest have one event control statement per procedure, at the top of the procedure - 15 | @e val = 2; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_timing_intra_assign_event.v:16:5: Unsupported: fork statements - : ... In instance t - 16 | fork begin - | ^~~~ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_event.v:21:11: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 21 | val = @e val + 2; - | ^ - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_timing_intra_assign_event.v:22:12: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 22 | val <= @e val + 2; - | ^ -%Error-UNSUPPORTED: t/t_timing_intra_assign_event.v:23:5: Unsupported: fork statements - : ... In instance t - 23 | fork begin - | ^~~~ -%Warning-STMTDLY: t/t_timing_intra_assign_event.v:29:6: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 29 | #1 $write("*-* All Finished *-*\n"); - | ^ -%Warning-STMTDLY: t/t_timing_intra_assign_event.v:33:12: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 33 | initial #1 ->e; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_event.v b/test_regress/t/t_timing_intra_assign_event.v deleted file mode 100644 index d98da3a66..000000000 --- a/test_regress/t/t_timing_intra_assign_event.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2022 by Antmicro Ltd. -// SPDX-License-Identifier: CC0-1.0 - -module t; - int val; - event e; - - always @val $write("val=%0d\n", val); - - initial begin - val = 1; - @e val = 2; - fork begin - @e #1 val = 3; - ->e; - end join_none - ->e; - val = @e val + 2; - val <= @e val + 2; - fork begin - @e val = 5; - ->e; - end join_none - ->e; - ->e; - #1 $write("*-* All Finished *-*\n"); - $finish; - end - - initial #1 ->e; -endmodule diff --git a/test_regress/t/t_timing_localevent_unsup.out b/test_regress/t/t_timing_localevent_unsup.out new file mode 100644 index 000000000..46578cb94 --- /dev/null +++ b/test_regress/t/t_timing_localevent_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_timing_localevent_unsup.v:12:17: Unsupported: waiting on local event variables + : ... In instance t::Sleeper + 12 | @e; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_timing_localevent_unsup.pl b/test_regress/t/t_timing_localevent_unsup.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_timing_localevent_unsup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_localevent_unsup.v b/test_regress/t/t_timing_localevent_unsup.v new file mode 100644 index 000000000..8091e2a4b --- /dev/null +++ b/test_regress/t/t_timing_localevent_unsup.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + class Sleeper; + task sleep; + event e; + fork + @e; + #1 ->e; + join; + endtask + endclass + + initial begin + Sleeper sleeper = new; + sleeper.sleep; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_long.pl b/test_regress/t/t_timing_long.pl index eb279f82e..3efef3dff 100755 --- a/test_regress/t/t_timing_long.pl +++ b/test_regress/t/t_timing_long.pl @@ -54,9 +54,22 @@ top_filename("$Self->{obj_dir}/t_timing_long.v"); gen($Self->{top_filename}); +if ($Self->have_coroutines) { + compile( + verilator_flags2 => ["--exe --build --main --timing"], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + make_top => 1, + ); + + execute( + check_finished => 1, + ); +} + compile( - # verilator_flags2 => ["--exe --build --main --timing"], # Unsupported - verilator_flags2 => ["--exe --build --main -Wno-STMTDLY"], + verilator_flags2 => ["--exe --build --main --no-timing -Wno-STMTDLY"], verilator_make_cmake => 0, verilator_make_gmake => 0, make_main => 0, diff --git a/test_regress/t/t_timing_nba.pl b/test_regress/t/t_timing_nba.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_nba.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_nba.v b/test_regress/t/t_timing_nba.v new file mode 100644 index 000000000..9554a5ad3 --- /dev/null +++ b/test_regress/t/t_timing_nba.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + integer cyc = 0; + + reg [7:0] a; + reg [127:0] b; + + always #1 begin + cyc <= cyc + 1; + if (cyc == 0) begin + a <= 8'hFF; + a[7] <= 1'b0; + end + else if (cyc == 1) begin +`ifdef TEST_VERBOSE + $write("a = %x\n", a); +`endif + if (a != 8'h7F) $stop; + end + else if (cyc == 2) begin + b <= 128'hFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + b[127] <= 1'b0; + end + else if (cyc == 3) begin +`ifdef TEST_VERBOSE + $write("b = %x\n", b); +`endif + if (b != 128'h7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) $stop; + end + else if (cyc > 3) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_timing_net_delay.out b/test_regress/t/t_timing_net_delay.out deleted file mode 100644 index 116ccc0a0..000000000 --- a/test_regress/t/t_timing_net_delay.out +++ /dev/null @@ -1,11 +0,0 @@ -%Warning-ASSIGNDLY: t/t_timing_net_delay.v:13:15: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 13 | wire[3:0] #4 val1 = cyc; - | ^ - ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_timing_net_delay.v:17:12: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 17 | assign #4 val2 = cyc; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_timing_off.pl b/test_regress/t/t_timing_off.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_off.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_off.v b/test_regress/t/t_timing_off.v new file mode 100644 index 000000000..1f25a7670 --- /dev/null +++ b/test_regress/t/t_timing_off.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e1; + event e2; + + initial begin + int x; + // verilator timing_off + #1 + fork @e1; @e2; join; + @e1 + wait(x == 4) + x = #1 8; + // verilator timing_on + if (x != 8) $stop; + if ($time != 0) $stop; + // verilator timing_off + @e2; + // verilator timing_on + @e1; + if ((e1.triggered && e2.triggered) + || (!e1.triggered && !e2.triggered)) $stop; + if ($time != 2) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + + initial #2 ->e1; + // verilator timing_off + initial #2 ->e2; + // verilator timing_on + initial #3 $stop; // timeout + initial #1 @(e1, e2) #1 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_pong.pl b/test_regress/t/t_timing_pong.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_pong.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_pong.v b/test_regress/t/t_timing_pong.v new file mode 100644 index 000000000..a9972a4a3 --- /dev/null +++ b/test_regress/t/t_timing_pong.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event ping; + event pong; + + int cnt = 0; + + initial forever @ping begin +`ifdef TEST_VERBOSE + $write("ping\n"); +`endif + cnt++; + ->pong; + end + + initial forever @pong begin +`ifdef TEST_VERBOSE + $write("pong\n"); +`endif + if (cnt < 10) ->ping; + end + + initial #1 ->ping; + initial #2 + if (cnt == 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end else $stop; + initial #3 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_protect.pl b/test_regress/t/t_timing_protect.pl new file mode 100755 index 000000000..bd2ed322d --- /dev/null +++ b/test_regress/t/t_timing_protect.pl @@ -0,0 +1,44 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_timing_fork_join.v"); # Contains all relevant constructs + + compile( + verilator_flags2 => ["--exe --main --timing --protect-ids", + "--protect-key SECRET_KEY"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); + + if ($Self->{vlt_all}) { + # Check for secret in any outputs + my $any; + foreach my $filename (glob $Self->{obj_dir} . "/*.[ch]*") { + file_grep_not($filename, qr/event[123]/i); + file_grep_not($filename, qr/t_timing_fork_join/i); + $any = 1; + } + $any or $Self->error("No outputs found"); + +} + +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_reentry.pl b/test_regress/t/t_timing_reentry.pl index dec83b460..f86c4b944 100755 --- a/test_regress/t/t_timing_reentry.pl +++ b/test_regress/t/t_timing_reentry.pl @@ -2,7 +2,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2019 by Wilson Snyder. This program is free software; you +# Copyright 2022 by Antmicro Ltd. 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. @@ -10,18 +10,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -compile( - #verilator_flags2 => ['--exe --build --main --timing'], # Unsupported - verilator_flags2 => ['--exe --build --main --bbox-unsup -Wno-STMTDLY -Wno-INITIALDLY'], - verilator_make_cmake => 0, - verilator_make_gmake => 0, - make_main => 0, - make_top => 1, - ); +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); -execute( - check_finished => 1, - ) if !$Self->{vlt_all}; + execute( + check_finished => 1, + ); +} ok(1); 1; diff --git a/test_regress/t/t_timing_sched.pl b/test_regress/t/t_timing_sched.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_sched.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_sched.v b/test_regress/t/t_timing_sched.v new file mode 100644 index 000000000..57fa10fcd --- /dev/null +++ b/test_regress/t/t_timing_sched.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk1 = 0; + + assign #3 clk1 = ~clk1; + + logic clk2 = 0; + assign #11 clk2 = ~clk2; + + int a1 = 0; + int b1 = 0; + always @(posedge clk1) #4 a1 = a1 + 1; + always @(posedge clk1) @(posedge clk2) b1 = b1 + 1; + + int a2 = 0; + always_comb begin + a2 = a1 << 1; +`ifdef TEST_VERBOSE + $display("[%0t] a2 = %0d", $time, a2); +`endif + end + + int b2 = 0; + always_comb begin + b2 = b1 << 2; +`ifdef TEST_VERBOSE + $display("[%0t] b2 = %0d", $time, b2); +`endif + end + + // verilator lint_off UNOPTFLAT + int c1 = 0; + int c2 = 0; + always @(b2, c1) begin + c2 = c1 >> 3; + c1 = b2 << 3; + end + // verilator lint_on UNOPTFLAT + + always @(posedge clk1) if (a2 != a1 << 1) $stop; + always @(posedge clk2) #1 if (b2 != b1 << 2) $stop; + + initial #78 begin +`ifdef TEST_VERBOSE + $display("a1=%0d, b1=%0d, a2=%0d, b2=%0d, c1=%0d, c2=%0d", a1, b1, a2, b2, c1, c2); +`endif + if (a1 != 12) $stop; + if (b1 != 4) $stop; + if (a2 != a1 << 1) $stop; + if (b2 != b1 << 2) $stop; + if (c1 != b2 << 3) $stop; + if (c2 != c1 >> 3) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_sched_if.pl b/test_regress/t/t_timing_sched_if.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_sched_if.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_sched_if.v b/test_regress/t/t_timing_sched_if.v new file mode 100644 index 000000000..faa84e266 --- /dev/null +++ b/test_regress/t/t_timing_sched_if.v @@ -0,0 +1,69 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk1 = 0; + + assign #3 clk1 = ~clk1; + + logic clk2 = 0; + assign #11 clk2 = ~clk2; + + logic flag = 0; + int a1 = 0; + int b1 = 0; + int c1 = 0; + always @(posedge clk1) begin + if (flag) #4 a1 = a1 + 1; + else @(posedge clk2) b1 = b1 + 1; + c1 = c1 + 1; + flag = ~flag; + end + + int a2 = 0; + always_comb begin + a2 = a1 << 1; +`ifdef TEST_VERBOSE + $display("[%0t] a2 = %0d", $time, a2); +`endif + end + + int b2 = 0; + always_comb begin + b2 = b1 << 2; +`ifdef TEST_VERBOSE + $display("[%0t] b2 = %0d", $time, b2); +`endif + end + + int c2 = 0; + always_comb begin + c2 = c1 << 3; +`ifdef TEST_VERBOSE + $display("[%0t] c2 = %0d", $time, c2); +`endif + end + + always @(posedge clk1) begin + #1 if (c2 != c1 << 3) $stop; + #5 if (a2 != a1 << 1) $stop; + end + always @(posedge clk2) #1 if (b2 != b1 << 2) $stop; + + initial #78 begin +`ifdef TEST_VERBOSE + $display("a1=%0d, b1=%0d, c1=%0d, a2=%0d, b2=%0d, c2=%0d", a1, b1, c1, a2, b2, c2); +`endif + if (a1 != 3) $stop; + if (b1 != 4) $stop; + if (c1 != a1 + b1) $stop; + if (a2 != a1 << 1) $stop; + if (b2 != b1 << 2) $stop; + if (c2 != c1 << 3) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_sched_nba.pl b/test_regress/t/t_timing_sched_nba.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_sched_nba.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_sched_nba.v b/test_regress/t/t_timing_sched_nba.v new file mode 100644 index 000000000..4106d755c --- /dev/null +++ b/test_regress/t/t_timing_sched_nba.v @@ -0,0 +1,50 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk1 = 0; + + assign #3 clk1 = ~clk1; + + logic clk2 = 0; + assign #11 clk2 = ~clk2; + + int a1 = 0; + int b1 = 0; + always @(posedge clk1) #4 a1 <= a1 + 1; + always @(posedge clk1) @(posedge clk2) b1 <= b1 + 1; + + int a2 = 0; + always_comb begin + a2 = a1 << 1; +`ifdef TEST_VERBOSE + $display("[%0t] a2 = %0d", $time, a2); +`endif + end + + int b2 = 0; + always_comb begin + b2 = b1 << 2; +`ifdef TEST_VERBOSE + $display("[%0t] b2 = %0d", $time, b2); +`endif + end + + always @(posedge clk1) #5 if (a2 != a1 << 1) $stop; + always @(posedge clk2) #1 if (b2 != b1 << 2) $stop; + + initial #78 begin +`ifdef TEST_VERBOSE + $display("a1=%0d, b1=%0d, a2=%0d, b2=%0d", a1, b1, a2, b2); +`endif + if (a1 != 12) $stop; + if (b1 != 4) $stop; + if (a2 != a1 << 1) $stop; + if (b2 != b1 << 2) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_strobe.out b/test_regress/t/t_timing_strobe.out new file mode 100644 index 000000000..01a73482a --- /dev/null +++ b/test_regress/t/t_timing_strobe.out @@ -0,0 +1,4 @@ +v = 1 +v = 2 +v = 3 +*-* All Finished *-* diff --git a/test_regress/t/t_timing_strobe.pl b/test_regress/t/t_timing_strobe.pl new file mode 100755 index 000000000..4ab3c9652 --- /dev/null +++ b/test_regress/t/t_timing_strobe.pl @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_strobe.v b/test_regress/t/t_timing_strobe.v new file mode 100644 index 000000000..8c1057fb4 --- /dev/null +++ b/test_regress/t/t_timing_strobe.v @@ -0,0 +1,31 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + event e1; + event e2; + int v = 0; + + initial begin + #1 $strobe("v = %0d", v); ->e1; + @e2 $strobe("v = %0d", v); ->e1; + @e2 $strobe("v = %0d", v); ->e1; + @e2 $write("*-* All Finished *-*\n"); + $finish; + end + + initial begin + @e1 v = 1; #1 ->e2; + @e1 v = 2; #1 ->e2; + @e1 v = 3; #1 ->e2; + end + + initial #5 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_trace.out b/test_regress/t/t_timing_trace.out new file mode 100644 index 000000000..f4c2f96b8 --- /dev/null +++ b/test_regress/t/t_timing_trace.out @@ -0,0 +1,76 @@ +$version Generated by VerilatedVcd $end +$date Thu Oct 20 09:56:59 2022 $end +$timescale 1ps $end + + $scope module TOP $end + $scope module t $end + $var wire 32 * CLK_HALF_PERIOD [31:0] $end + $var wire 32 ) CLK_PERIOD [31:0] $end + $var wire 1 $ a $end + $var wire 1 % b $end + $var wire 1 & c $end + $var wire 1 ( clk $end + $var wire 1 ' d $end + $var wire 1 # rst $end + $upscope $end + $upscope $end +$enddefinitions $end + + +#0 +1# +0$ +0% +0& +0' +0( +b00000000000000000000000000001010 ) +b00000000000000000000000000000101 * +#5 +1( +#10 +0# +1% +0( +#15 +1( +#20 +0( +#25 +1( +#30 +0( +#35 +1( +#40 +0( +#45 +1( +#50 +0( +#55 +1( +#60 +0( +#65 +1( +#70 +0( +#75 +1( +#80 +0( +#85 +1( +#90 +0( +#95 +1( +#100 +0( +#105 +1( +#110 +1# +0% +0( diff --git a/test_regress/t/t_timing_trace.pl b/test_regress/t/t_timing_trace.pl new file mode 100755 index 000000000..6a98bee9d --- /dev/null +++ b/test_regress/t/t_timing_trace.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing --trace -Wno-MINTYPMAXDLY"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); + + vcd_identical($Self->trace_filename, $Self->{golden_filename}); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_trace.v b/test_regress/t/t_timing_trace.v new file mode 100644 index 000000000..25782e267 --- /dev/null +++ b/test_regress/t/t_timing_trace.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`define STRINGIFY(x) `"x`" + +module t; + localparam CLK_PERIOD = 10; + localparam CLK_HALF_PERIOD = CLK_PERIOD / 2; + + logic rst; + logic clk; + logic a; + logic b; + logic c; + logic d; + + initial begin + $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); + $dumpvars; + forever clk = #CLK_HALF_PERIOD ~clk; + end + + always begin + rst = 1; + clk = 0; + a = 0; + c = 0; + b = 0; + d = 0; + + #CLK_PERIOD; + rst = 0; + b = 1; + + #(10 * CLK_PERIOD); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_unset1.out b/test_regress/t/t_timing_unset1.out new file mode 100644 index 000000000..c6bcbb64a --- /dev/null +++ b/test_regress/t/t_timing_unset1.out @@ -0,0 +1,34 @@ +%Error-NEEDTIMINGOPT: t/t_notiming.v:12:8: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 12 | #1 + | ^ + ... For error description see https://verilator.org/warn/NEEDTIMINGOPT?v=latest +%Error-NEEDTIMINGOPT: t/t_notiming.v:13:8: Use --timing or --no-timing to specify how forks should be handled + : ... In instance t + 13 | fork @e; @e; join; + | ^~~~ +%Error-NEEDTIMINGOPT: t/t_notiming.v:14:8: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 14 | @e + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:15:8: Use --timing or --no-timing to specify how wait statements should be handled + : ... In instance t + 15 | wait(x == 4) + | ^~~~ +%Error-NEEDTIMINGOPT: t/t_notiming.v:16:12: Use --timing or --no-timing to specify how timing controls should be handled + : ... In instance t + 16 | x = #1 8; + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:19:8: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 19 | @e + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:26:12: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 26 | initial #1 ->e; + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:27:12: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 27 | initial #2 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_unset1.pl b/test_regress/t/t_timing_unset1.pl new file mode 100755 index 000000000..167a6cf70 --- /dev/null +++ b/test_regress/t/t_timing_unset1.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_notiming.v"); + +compile( + # --timing/--no-timing not specified + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_unset2.out b/test_regress/t/t_timing_unset2.out new file mode 100644 index 000000000..0d340a8f2 --- /dev/null +++ b/test_regress/t/t_timing_unset2.out @@ -0,0 +1,26 @@ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:25:8: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 25 | @e1; + | ^ + ... For error description see https://verilator.org/warn/NEEDTIMINGOPT?v=latest +%Error-NEEDTIMINGOPT: t/t_timing_off.v:33:12: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 33 | initial #2 ->e1; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:37:12: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 37 | initial #3 $stop; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:12: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:15: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:25: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_unset2.pl b/test_regress/t/t_timing_unset2.pl new file mode 100755 index 000000000..be9ccffa3 --- /dev/null +++ b/test_regress/t/t_timing_unset2.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_timing_off.v"); + +compile( + # --timing/--no-timing not specified + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_wait.pl b/test_regress/t/t_timing_wait.pl new file mode 100755 index 000000000..ecf974b24 --- /dev/null +++ b/test_regress/t/t_timing_wait.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing -Wno-WAITCONST"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_wait.v b/test_regress/t/t_timing_wait.v new file mode 100644 index 000000000..abfda1c09 --- /dev/null +++ b/test_regress/t/t_timing_wait.v @@ -0,0 +1,55 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(msg) $write(msg) +`else + `define WRITE_VERBOSE(msg) +`endif + +module t; + int a = 0; + int b = 0; + int c = 0; + + initial begin + `WRITE_VERBOSE("start with a==0, b==0, c==0\n"); + #2 a = 1; `WRITE_VERBOSE("assign 1 to a\n"); + #1 a = 2; `WRITE_VERBOSE("assign 2 to a\n"); // a==2 + #1 a = 0; `WRITE_VERBOSE("assign 0 to a\n"); + #1 a = 2; `WRITE_VERBOSE("assign 2 to a\n"); // 1a + #1 c = 3; `WRITE_VERBOSE("assign 3 to c\n"); + #1 c = 4; `WRITE_VERBOSE("assign 4 to c\n"); // a+bc + b = 5; + end + + initial begin + #1 `WRITE_VERBOSE("waiting for a==2\n"); + wait(a == 2) if (a != 2) $stop; + `WRITE_VERBOSE("waiting for a<2\n"); + wait(a < 2) if (a >= 2) $stop; + `WRITE_VERBOSE("waiting for a==0\n"); + wait(a == 0) if (a != 0) $stop; + `WRITE_VERBOSE("waiting for 1 1 && a < 3) if (a <= 1 || a >= 3) $stop; + `WRITE_VERBOSE("waiting for b>a\n"); + wait(b > a) if (b <= a) $stop; + `WRITE_VERBOSE("waiting for a+b= c) $stop; + `WRITE_VERBOSE("waiting for ac\n"); + wait(a < b && b > c) if (a >= b || b <= c) $stop; + wait(0 < 1) $write("*-* All Finished *-*\n"); + $finish; + end + + initial wait(0) $stop; + initial wait(1 == 0) $stop; + + initial #11 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_zerodly_unsup.out b/test_regress/t/t_timing_zerodly_unsup.out new file mode 100644 index 000000000..05ffbd7fb --- /dev/null +++ b/test_regress/t/t_timing_zerodly_unsup.out @@ -0,0 +1,5 @@ +%Error-ZERODLY: t/t_timing_zerodly_unsup.v:12:13: Unsupported: #0 delays do not schedule process resumption in the Inactive region + 12 | #0 if (v) $finish; + | ^ + ... For error description see https://verilator.org/warn/ZERODLY?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_timing_zerodly_unsup.pl b/test_regress/t/t_timing_zerodly_unsup.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_timing_zerodly_unsup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_zerodly_unsup.v b/test_regress/t/t_timing_zerodly_unsup.v new file mode 100644 index 000000000..7fba70252 --- /dev/null +++ b/test_regress/t/t_timing_zerodly_unsup.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e; + logic v = 0; + initial #1 begin + fork + #0 if (v) $finish; + else $stop; + join_none + ->e; + end + initial @e v = 1; +endmodule diff --git a/test_regress/t/t_trace_binary.out b/test_regress/t/t_trace_binary.out new file mode 100644 index 000000000..32d4f8ec5 --- /dev/null +++ b/test_regress/t/t_trace_binary.out @@ -0,0 +1,16 @@ +$version Generated by VerilatedVcd $end +$date Sun Oct 9 14:08:37 2022 $end +$timescale 1ps $end + + $scope module TOP $end + $scope module t $end + $var wire 32 # sig [31:0] $end + $upscope $end + $upscope $end +$enddefinitions $end + + +#0 +b00000000000000000000000000001010 # +#20 +b00000000000000000000000000010100 # diff --git a/test_regress/t/t_trace_binary.pl b/test_regress/t/t_trace_binary.pl new file mode 100755 index 000000000..03fd8a3c5 --- /dev/null +++ b/test_regress/t/t_trace_binary.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary --trace'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + check_finished => 1, + ); + + vcd_identical("$Self->{obj_dir}/simx.vcd", $Self->{golden_filename}); +} + +ok(1); +1; diff --git a/test_regress/t/t_delay_func_bad.v b/test_regress/t/t_trace_binary.v similarity index 67% rename from test_regress/t/t_delay_func_bad.v rename to test_regress/t/t_trace_binary.v index aa7ceddfc..445c2b375 100644 --- a/test_regress/t/t_delay_func_bad.v +++ b/test_regress/t/t_trace_binary.v @@ -4,24 +4,17 @@ // any use, without warranty, 2020 by Wilson Snyder. // SPDX-License-Identifier: CC0-1.0 +`define STRINGIFY(x) `"x`" + module t(/*AUTOARG*/); - - function int f; - #1 $stop; - f = 0; - endfunction - - int i; - + int sig; initial begin - i = f(); + sig = 10; + $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); + $dumpvars(); + #20; + sig = 20; $write("*-* All Finished *-*\n"); $finish; end - - final begin - #1; - $stop; - end - endmodule diff --git a/test_regress/t/t_trace_binary_flag_off.out b/test_regress/t/t_trace_binary_flag_off.out new file mode 100644 index 000000000..8c40ee2bf --- /dev/null +++ b/test_regress/t/t_trace_binary_flag_off.out @@ -0,0 +1,2 @@ +-Info: t/t_trace_binary.v:14: $dumpvar ignored, as Verilated without --trace +*-* All Finished *-* diff --git a/test_regress/t/t_trace_binary_flag_off.pl b/test_regress/t/t_trace_binary_flag_off.pl new file mode 100755 index 000000000..eaa6a3518 --- /dev/null +++ b/test_regress/t/t_trace_binary_flag_off.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +top_filename("t/t_trace_binary.v"); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_trace_complex.out b/test_regress/t/t_trace_complex.out index 62c142c2b..0bff7fdc8 100644 --- a/test_regress/t/t_trace_complex.out +++ b/test_regress/t/t_trace_complex.out @@ -1,56 +1,56 @@ $version Generated by VerilatedVcd $end -$date Wed Aug 11 12:40:46 2021 $end +$date Sun May 8 19:00:11 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 = clk $end + $var wire 1 $ clk $end $scope module $unit $end $var wire 1 # global_bit $end $upscope $end $scope module t $end $var wire 1 G LONGSTART_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_LONGEND $end - $var wire 1 = clk $end - $var wire 32 $ cyc [31:0] $end + $var wire 1 $ clk $end + $var wire 32 % cyc [31:0] $end $var wire 8 E unpacked_array[-1] [7:0] $end $var wire 8 D unpacked_array[-2] [7:0] $end $var wire 8 F unpacked_array[0] [7:0] $end - $var real 64 1 v_arr_real[0] $end - $var real 64 3 v_arr_real[1] $end - $var wire 2 ( v_arrp [2:1] $end - $var wire 4 ) v_arrp_arrp [3:0] $end - $var wire 4 * v_arrp_strp [3:0] $end + $var real 64 2 v_arr_real[0] $end + $var real 64 4 v_arr_real[1] $end + $var wire 2 ) v_arrp [2:1] $end + $var wire 4 * v_arrp_arrp [3:0] $end + $var wire 4 + v_arrp_strp [3:0] $end $var wire 1 > v_arru[1] $end $var wire 1 ? v_arru[2] $end - $var wire 2 + v_arru_arrp[3] [2:1] $end - $var wire 2 , v_arru_arrp[4] [2:1] $end + $var wire 2 , v_arru_arrp[3] [2:1] $end + $var wire 2 - v_arru_arrp[4] [2:1] $end $var wire 1 @ v_arru_arru[3][1] $end $var wire 1 A v_arru_arru[3][2] $end $var wire 1 B v_arru_arru[4][1] $end $var wire 1 C v_arru_arru[4][2] $end - $var wire 2 - v_arru_strp[3] [1:0] $end - $var wire 2 . v_arru_strp[4] [1:0] $end - $var wire 3 9 v_enumb [2:0] $end - $var wire 6 : v_enumb2_str [5:0] $end - $var wire 32 7 v_enumed [31:0] $end - $var wire 32 8 v_enumed2 [31:0] $end - $var real 64 / v_real $end - $var wire 64 5 v_str32x2 [63:0] $end - $var wire 2 % v_strp [1:0] $end - $var wire 4 & v_strp_strp [3:0] $end - $var wire 2 ' v_unip_strp [1:0] $end + $var wire 2 . v_arru_strp[3] [1:0] $end + $var wire 2 / v_arru_strp[4] [1:0] $end + $var wire 3 : v_enumb [2:0] $end + $var wire 6 ; v_enumb2_str [5:0] $end + $var wire 32 8 v_enumed [31:0] $end + $var wire 32 9 v_enumed2 [31:0] $end + $var real 64 0 v_real $end + $var wire 64 6 v_str32x2 [63:0] $end + $var wire 2 & v_strp [1:0] $end + $var wire 4 ' v_strp_strp [3:0] $end + $var wire 2 ( v_unip_strp [1:0] $end $scope module a_module_instantiation_with_a_very_long_name_that_once_its_signals_get_concatenated_and_inlined_will_almost_certainly_result_in_them_getting_hashed $end - $var wire 32 J PARAM [31:0] $end - $upscope $end - $scope module p2 $end $var wire 32 H PARAM [31:0] $end $upscope $end - $scope module p3 $end + $scope module p2 $end $var wire 32 I PARAM [31:0] $end $upscope $end + $scope module p3 $end + $var wire 32 J PARAM [31:0] $end + $upscope $end $scope module unnamedblk1 $end - $var wire 32 ; b [31:0] $end + $var wire 32 < b [31:0] $end $scope module unnamedblk2 $end - $var wire 32 < a [31:0] $end + $var wire 32 = a [31:0] $end $upscope $end $upscope $end $upscope $end @@ -60,28 +60,28 @@ $enddefinitions $end #0 1# -b00000000000000000000000000000000 $ -b00 % -b0000 & -b00 ' +0$ +b00000000000000000000000000000000 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0 / -r0 1 -r0 3 -b0000000000000000000000000000000000000000000000000000000011111111 5 -b00000000000000000000000000000000 7 +b00 / +r0 0 +r0 2 +r0 4 +b0000000000000000000000000000000000000000000000000000000011111111 6 b00000000000000000000000000000000 8 -b000 9 -b000000 : -b00000000000000000000000000000000 ; +b00000000000000000000000000000000 9 +b000 : +b000000 ; b00000000000000000000000000000000 < -0= +b00000000000000000000000000000000 = 0> 0? 0@ @@ -92,143 +92,143 @@ b00000000 D b00000000 E b00000000 F 0G -b00000000000000000000000000000010 H -b00000000000000000000000000000011 I -b00000000000000000000000000000100 J +b00000000000000000000000000000100 H +b00000000000000000000000000000010 I +b00000000000000000000000000000011 J #10 -b00000000000000000000000000000001 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000001 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.1 / -r0.2 1 -r0.3 3 -b0000000000000000000000000000000100000000000000000000000011111110 5 -b00000000000000000000000000000001 7 -b00000000000000000000000000000010 8 -b111 9 -b00000000000000000000000000000101 ; +b11 / +r0.1 0 +r0.2 2 +r0.3 4 +b0000000000000000000000000000000100000000000000000000000011111110 6 +b00000000000000000000000000000001 8 +b00000000000000000000000000000010 9 +b111 : b00000000000000000000000000000101 < -1= +b00000000000000000000000000000101 = #15 -0= +0$ #20 -b00000000000000000000000000000010 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000010 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.2 / -r0.4 1 -r0.6 3 -b0000000000000000000000000000001000000000000000000000000011111101 5 -b00000000000000000000000000000010 7 -b00000000000000000000000000000100 8 -b110 9 -b111111 : -1= +b00 / +r0.2 0 +r0.4 2 +r0.6 4 +b0000000000000000000000000000001000000000000000000000000011111101 6 +b00000000000000000000000000000010 8 +b00000000000000000000000000000100 9 +b110 : +b111111 ; #25 -0= +0$ #30 -b00000000000000000000000000000011 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000011 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.3 / -r0.6000000000000001 1 -r0.8999999999999999 3 -b0000000000000000000000000000001100000000000000000000000011111100 5 -b00000000000000000000000000000011 7 -b00000000000000000000000000000110 8 -b101 9 -b110110 : -1= +b11 / +r0.3 0 +r0.6000000000000001 2 +r0.8999999999999999 4 +b0000000000000000000000000000001100000000000000000000000011111100 6 +b00000000000000000000000000000011 8 +b00000000000000000000000000000110 9 +b101 : +b110110 ; #35 -0= +0$ #40 -b00000000000000000000000000000100 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000100 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.4 / -r0.8 1 -r1.2 3 -b0000000000000000000000000000010000000000000000000000000011111011 5 -b00000000000000000000000000000100 7 -b00000000000000000000000000001000 8 -b100 9 -b101101 : -1= +b00 / +r0.4 0 +r0.8 2 +r1.2 4 +b0000000000000000000000000000010000000000000000000000000011111011 6 +b00000000000000000000000000000100 8 +b00000000000000000000000000001000 9 +b100 : +b101101 ; #45 -0= +0$ #50 -b00000000000000000000000000000101 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000101 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.5 / -r1 1 -r1.5 3 -b0000000000000000000000000000010100000000000000000000000011111010 5 -b00000000000000000000000000000101 7 -b00000000000000000000000000001010 8 -b011 9 -b100100 : -1= +b11 / +r0.5 0 +r1 2 +r1.5 4 +b0000000000000000000000000000010100000000000000000000000011111010 6 +b00000000000000000000000000000101 8 +b00000000000000000000000000001010 9 +b011 : +b100100 ; #55 -0= +0$ #60 -b00000000000000000000000000000110 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000110 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.6 / -r1.2 1 -r1.8 3 -b0000000000000000000000000000011000000000000000000000000011111001 5 -b00000000000000000000000000000110 7 -b00000000000000000000000000001100 8 -b010 9 -b011011 : -1= +b00 / +r0.6 0 +r1.2 2 +r1.8 4 +b0000000000000000000000000000011000000000000000000000000011111001 6 +b00000000000000000000000000000110 8 +b00000000000000000000000000001100 9 +b010 : +b011011 ; diff --git a/test_regress/t/t_trace_complex_params.out b/test_regress/t/t_trace_complex_params.out index 4fd754aed..96cca0314 100644 --- a/test_regress/t/t_trace_complex_params.out +++ b/test_regress/t/t_trace_complex_params.out @@ -1,56 +1,56 @@ $version Generated by VerilatedVcd $end -$date Wed Aug 11 12:41:11 2021 $end +$date Sun May 8 19:00:32 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 = clk $end + $var wire 1 $ clk $end $scope module $unit $end $var wire 1 # global_bit $end $upscope $end $scope module t $end $var wire 1 G LONGSTART_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_LONGEND $end - $var wire 1 = clk $end - $var wire 32 $ cyc [31:0] $end + $var wire 1 $ clk $end + $var wire 32 % cyc [31:0] $end $var wire 8 E unpacked_array[-1] [7:0] $end $var wire 8 D unpacked_array[-2] [7:0] $end $var wire 8 F unpacked_array[0] [7:0] $end - $var real 64 1 v_arr_real[0] $end - $var real 64 3 v_arr_real[1] $end - $var wire 2 ( v_arrp [2:1] $end - $var wire 4 ) v_arrp_arrp [3:0] $end - $var wire 4 * v_arrp_strp [3:0] $end + $var real 64 2 v_arr_real[0] $end + $var real 64 4 v_arr_real[1] $end + $var wire 2 ) v_arrp [2:1] $end + $var wire 4 * v_arrp_arrp [3:0] $end + $var wire 4 + v_arrp_strp [3:0] $end $var wire 1 > v_arru[1] $end $var wire 1 ? v_arru[2] $end - $var wire 2 + v_arru_arrp[3] [2:1] $end - $var wire 2 , v_arru_arrp[4] [2:1] $end + $var wire 2 , v_arru_arrp[3] [2:1] $end + $var wire 2 - v_arru_arrp[4] [2:1] $end $var wire 1 @ v_arru_arru[3][1] $end $var wire 1 A v_arru_arru[3][2] $end $var wire 1 B v_arru_arru[4][1] $end $var wire 1 C v_arru_arru[4][2] $end - $var wire 2 - v_arru_strp[3] [1:0] $end - $var wire 2 . v_arru_strp[4] [1:0] $end - $var wire 3 9 v_enumb [2:0] $end - $var wire 6 : v_enumb2_str [5:0] $end - $var wire 32 7 v_enumed [31:0] $end - $var wire 32 8 v_enumed2 [31:0] $end - $var real 64 / v_real $end - $var wire 64 5 v_str32x2 [63:0] $end - $var wire 2 % v_strp [1:0] $end - $var wire 4 & v_strp_strp [3:0] $end - $var wire 2 ' v_unip_strp [1:0] $end + $var wire 2 . v_arru_strp[3] [1:0] $end + $var wire 2 / v_arru_strp[4] [1:0] $end + $var wire 3 : v_enumb [2:0] $end + $var wire 6 ; v_enumb2_str [5:0] $end + $var wire 32 8 v_enumed [31:0] $end + $var wire 32 9 v_enumed2 [31:0] $end + $var real 64 0 v_real $end + $var wire 64 6 v_str32x2 [63:0] $end + $var wire 2 & v_strp [1:0] $end + $var wire 4 ' v_strp_strp [3:0] $end + $var wire 2 ( v_unip_strp [1:0] $end $scope module a_module_instantiation_with_a_very_long_name_that_once_its_signals_get_concatenated_and_inlined_will_almost_certainly_result_in_them_getting_hashed $end - $var wire 32 J PARAM [31:0] $end - $upscope $end - $scope module p2 $end $var wire 32 H PARAM [31:0] $end $upscope $end - $scope module p3 $end + $scope module p2 $end $var wire 32 I PARAM [31:0] $end $upscope $end + $scope module p3 $end + $var wire 32 J PARAM [31:0] $end + $upscope $end $scope module unnamedblk1 $end - $var wire 32 ; b [31:0] $end + $var wire 32 < b [31:0] $end $scope module unnamedblk2 $end - $var wire 32 < a [31:0] $end + $var wire 32 = a [31:0] $end $upscope $end $upscope $end $upscope $end @@ -60,28 +60,28 @@ $enddefinitions $end #0 1# -b00000000000000000000000000000000 $ -b00 % -b0000 & -b00 ' +0$ +b00000000000000000000000000000000 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0 / -r0 1 -r0 3 -b0000000000000000000000000000000000000000000000000000000011111111 5 -b00000000000000000000000000000000 7 +b00 / +r0 0 +r0 2 +r0 4 +b0000000000000000000000000000000000000000000000000000000011111111 6 b00000000000000000000000000000000 8 -b000 9 -b000000 : -b00000000000000000000000000000000 ; +b00000000000000000000000000000000 9 +b000 : +b000000 ; b00000000000000000000000000000000 < -0= +b00000000000000000000000000000000 = 0> 0? 0@ @@ -92,143 +92,143 @@ b00000000 D b00000000 E b00000000 F 0G -b00000000000000000000000000000010 H -b00000000000000000000000000000011 I -b00000000000000000000000000000100 J +b00000000000000000000000000000100 H +b00000000000000000000000000000010 I +b00000000000000000000000000000011 J #10 -b00000000000000000000000000000001 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000001 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.1 / -r0.2 1 -r0.3 3 -b0000000000000000000000000000000100000000000000000000000011111110 5 -b00000000000000000000000000000001 7 -b00000000000000000000000000000010 8 -b111 9 -b00000000000000000000000000000101 ; +b11 / +r0.1 0 +r0.2 2 +r0.3 4 +b0000000000000000000000000000000100000000000000000000000011111110 6 +b00000000000000000000000000000001 8 +b00000000000000000000000000000010 9 +b111 : b00000000000000000000000000000101 < -1= +b00000000000000000000000000000101 = #15 -0= +0$ #20 -b00000000000000000000000000000010 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000010 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.2 / -r0.4 1 -r0.6 3 -b0000000000000000000000000000001000000000000000000000000011111101 5 -b00000000000000000000000000000010 7 -b00000000000000000000000000000100 8 -b110 9 -b111111 : -1= +b00 / +r0.2 0 +r0.4 2 +r0.6 4 +b0000000000000000000000000000001000000000000000000000000011111101 6 +b00000000000000000000000000000010 8 +b00000000000000000000000000000100 9 +b110 : +b111111 ; #25 -0= +0$ #30 -b00000000000000000000000000000011 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000011 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.3 / -r0.6000000000000001 1 -r0.8999999999999999 3 -b0000000000000000000000000000001100000000000000000000000011111100 5 -b00000000000000000000000000000011 7 -b00000000000000000000000000000110 8 -b101 9 -b110110 : -1= +b11 / +r0.3 0 +r0.6000000000000001 2 +r0.8999999999999999 4 +b0000000000000000000000000000001100000000000000000000000011111100 6 +b00000000000000000000000000000011 8 +b00000000000000000000000000000110 9 +b101 : +b110110 ; #35 -0= +0$ #40 -b00000000000000000000000000000100 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000100 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.4 / -r0.8 1 -r1.2 3 -b0000000000000000000000000000010000000000000000000000000011111011 5 -b00000000000000000000000000000100 7 -b00000000000000000000000000001000 8 -b100 9 -b101101 : -1= +b00 / +r0.4 0 +r0.8 2 +r1.2 4 +b0000000000000000000000000000010000000000000000000000000011111011 6 +b00000000000000000000000000000100 8 +b00000000000000000000000000001000 9 +b100 : +b101101 ; #45 -0= +0$ #50 -b00000000000000000000000000000101 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000101 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.5 / -r1 1 -r1.5 3 -b0000000000000000000000000000010100000000000000000000000011111010 5 -b00000000000000000000000000000101 7 -b00000000000000000000000000001010 8 -b011 9 -b100100 : -1= +b11 / +r0.5 0 +r1 2 +r1.5 4 +b0000000000000000000000000000010100000000000000000000000011111010 6 +b00000000000000000000000000000101 8 +b00000000000000000000000000001010 9 +b011 : +b100100 ; #55 -0= +0$ #60 -b00000000000000000000000000000110 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000110 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.6 / -r1.2 1 -r1.8 3 -b0000000000000000000000000000011000000000000000000000000011111001 5 -b00000000000000000000000000000110 7 -b00000000000000000000000000001100 8 -b010 9 -b011011 : -1= +b00 / +r0.6 0 +r1.2 2 +r1.8 4 +b0000000000000000000000000000011000000000000000000000000011111001 6 +b00000000000000000000000000000110 8 +b00000000000000000000000000001100 9 +b010 : +b011011 ; diff --git a/test_regress/t/t_trace_dumporder_bad.out b/test_regress/t/t_trace_dumporder_bad.out index f73b7d67a..84fe64421 100644 --- a/test_regress/t/t_trace_dumporder_bad.out +++ b/test_regress/t/t_trace_dumporder_bad.out @@ -1,2 +1,2 @@ -%Warning: $dumpvar ignored as not proceeded by $dumpfile +%Warning: $dumpvar ignored as not preceded by $dumpfile *-* All Finished *-* diff --git a/test_regress/t/t_trace_timing1.out b/test_regress/t/t_trace_timing1.out new file mode 100644 index 000000000..1cd5d550c --- /dev/null +++ b/test_regress/t/t_trace_timing1.out @@ -0,0 +1,27 @@ +$version Generated by VerilatedVcd $end +$date Sat Oct 15 13:17:45 2022 $end +$timescale 1ps $end + + $scope module TOP $end + $scope module t $end + $var wire 32 % CLOCK_CYCLE [31:0] $end + $var wire 1 $ clk $end + $var wire 1 # rst $end + $upscope $end + $upscope $end +$enddefinitions $end + + +#0 +1# +0$ +b00000000000000000000000000001010 % +#5 +1$ +#10 +0# +0$ +#15 +1$ +#20 +1# diff --git a/test_regress/t/t_trace_timing1.pl b/test_regress/t/t_trace_timing1.pl new file mode 100755 index 000000000..2ae74250a --- /dev/null +++ b/test_regress/t/t_trace_timing1.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary --trace'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +vcd_identical("$Self->{obj_dir}/simx.vcd", $Self->{golden_filename}); + +ok(1); +1; diff --git a/test_regress/t/t_trace_timing1.v b/test_regress/t/t_trace_timing1.v new file mode 100644 index 000000000..26aef7ea9 --- /dev/null +++ b/test_regress/t/t_trace_timing1.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`define STRINGIFY(x) `"x`" + +module t(/*AUTOARG*/); + + localparam CLOCK_CYCLE = 10; + + logic rst; + logic clk; + + initial begin + $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); + $dumpvars; + end + + always #(CLOCK_CYCLE/2) clk = ~clk; + + always begin + rst = 1; + clk = 0; + $display("[%0t] rst: %d, rst: %d", $time, rst, rst); + + #CLOCK_CYCLE; + rst = 0; + $display("[%0t] rst: %d, rst: %d", $time, rst, rst); + + #CLOCK_CYCLE; + $display("[%0t] rst: %d, rst: %d", $time, rst, rst); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_trace_two_a.v b/test_regress/t/t_trace_two_a.v index a9b87db05..83f3ed363 100644 --- a/test_regress/t/t_trace_two_a.v +++ b/test_regress/t/t_trace_two_a.v @@ -32,7 +32,7 @@ module t (/*AUTOARG*/ `ifdef TEST_DUMP $dumpfile(filename); - $dumpvars(0, top); + $dumpvars(0); // Intentionally no ", top" for parsing coverage with just (expr) $dumplimit(10 * 1024 * 1024); `elsif TEST_DUMPPORTS $dumpports(top, filename); diff --git a/test_regress/t/t_tri_and_eqcase.out b/test_regress/t/t_tri_and_eqcase.out new file mode 100644 index 000000000..315a41e1f --- /dev/null +++ b/test_regress/t/t_tri_and_eqcase.out @@ -0,0 +1,8 @@ +%Error-UNSUPPORTED: t/t_tri_and_eqcase.v:9:28: Unsupported tristate construct: AND in function getEnExprBasedOnOriginalp + 9 | logic b = 1'z === (clk1 & clk2); + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Internal Error: t/t_tri_and_eqcase.v:9:18: ../V3Ast.cpp:#: Null item passed to setOp2p + 9 | logic b = 1'z === (clk1 & clk2); + | ^~~ + ... See the manual at https://verilator.org/verilator_doc.html for more assistance. diff --git a/test_regress/t/t_tri_and_eqcase.pl b/test_regress/t/t_tri_and_eqcase.pl new file mode 100755 index 000000000..48bf31461 --- /dev/null +++ b/test_regress/t/t_tri_and_eqcase.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_tri_and_eqcase.v b/test_regress/t/t_tri_and_eqcase.v new file mode 100644 index 000000000..204aab82d --- /dev/null +++ b/test_regress/t/t_tri_and_eqcase.v @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (clk1, clk2); + input wire clk1, clk2; + logic b = 1'z === (clk1 & clk2); + + always begin + if (!b) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_tri_select_eqcase.pl b/test_regress/t/t_tri_select_eqcase.pl new file mode 100755 index 000000000..f5e338520 --- /dev/null +++ b/test_regress/t/t_tri_select_eqcase.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_tri_select_eqcase.v b/test_regress/t/t_tri_select_eqcase.v new file mode 100644 index 000000000..c70688c0f --- /dev/null +++ b/test_regress/t/t_tri_select_eqcase.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + wire [3:0] a = 4'b11z1; + logic b = 1'bz === a[1]; + logic c = 1'bz === a[2]; + logic d = 2'bzz === 2'(a[1]); + logic e = 2'b0z === 2'(a[1]); + + + always begin + if (b && !c && !d && e) begin + $write("*-* All Finished *-*\n"); + $finish; + end + else begin + $write("Error: b = %b, c = %b, d = %b, e = %b ", b, c, d, e); + $write("expected: b = 1, c = 0, d = 0, e = 1\n"); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_udp_tableend_bad.out b/test_regress/t/t_udp_tableend_bad.out new file mode 100644 index 000000000..d673497d0 --- /dev/null +++ b/test_regress/t/t_udp_tableend_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_udp_tableend_bad.v:11:4: Syntax error: 'endtable' outside of 'table' + 11 | endtable + | ^~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_udp_tableend_bad.pl b/test_regress/t/t_udp_tableend_bad.pl new file mode 100755 index 000000000..fc220ea47 --- /dev/null +++ b/test_regress/t/t_udp_tableend_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only --bbox-unsup"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_udp_tableend_bad.v b/test_regress/t/t_udp_tableend_bad.v new file mode 100644 index 000000000..6597e7be6 --- /dev/null +++ b/test_regress/t/t_udp_tableend_bad.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2009 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +primitive udp_x (a_bad, b, c_bad); + tri a_bad; + output b; + output c_bad; + endtable // BAD +endprimitive diff --git a/test_regress/t/t_udp_tableeof_bad.pl b/test_regress/t/t_udp_tableeof_bad.pl new file mode 100755 index 000000000..677258838 --- /dev/null +++ b/test_regress/t/t_udp_tableeof_bad.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only --bbox-unsup"], + fails => $Self->{vlt_all}, + # Cannot use .out, get "$end" or "end of file" depending on bison version + expect => qr/EOF in 'table'/, + ); + +ok(1); +1; diff --git a/test_regress/t/t_udp_tableeof_bad.v b/test_regress/t/t_udp_tableeof_bad.v new file mode 100644 index 000000000..064573395 --- /dev/null +++ b/test_regress/t/t_udp_tableeof_bad.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2009 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +primitive udp_x (a_bad, b, c_bad); + tri a_bad; + output b; + output c_bad; + table + //a b + 0 : 1; + 1 : 0; diff --git a/test_regress/t/t_unbounded_bad.out b/test_regress/t/t_unbounded_bad.out new file mode 100644 index 000000000..bd98ae872 --- /dev/null +++ b/test_regress/t/t_unbounded_bad.out @@ -0,0 +1,11 @@ +%Error-UNSUPPORTED: t/t_unbounded_bad.v:9:11: Unsupported/illegal unbounded ('$') in this context. + : ... In instance t + 9 | if ($) $stop; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Warning-WIDTH: t/t_unbounded_bad.v:9:7: Logical operator IF expects 1 bit on the If, but If's UNBOUNDED generates 32 bits. + : ... In instance t + 9 | if ($) $stop; + | ^~ + ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_unbounded_bad.pl b/test_regress/t/t_unbounded_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_unbounded_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_unbounded_bad.v b/test_regress/t/t_unbounded_bad.v new file mode 100644 index 000000000..7927e7bfb --- /dev/null +++ b/test_regress/t/t_unbounded_bad.v @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + if ($) $stop; + end +endmodule diff --git a/test_regress/t/t_unopt_combo.vlt b/test_regress/t/t_unopt_combo.vlt index ffec0fb7a..21286681d 100644 --- a/test_regress/t/t_unopt_combo.vlt +++ b/test_regress/t/t_unopt_combo.vlt @@ -1,3 +1,3 @@ `verilator_config -lint_off -rule UNOPTFLAT -file "*t_unopt_combo.v" -match "Signal unoptimizable: Feedback to clock or circular logic: *" +lint_off -rule UNOPTFLAT -file "*t_unopt_combo.v" -match "Signal unoptimizable: Circular combinational logic: *" diff --git a/test_regress/t/t_unopt_combo_bad.out b/test_regress/t/t_unopt_combo_bad.out index 4e0c5f49b..8cd58f59f 100644 --- a/test_regress/t/t_unopt_combo_bad.out +++ b/test_regress/t/t_unopt_combo_bad.out @@ -1,11 +1,11 @@ -%Warning-UNOPTFLAT: t/t_unopt_combo.v:24:25: Signal unoptimizable: Feedback to clock or circular logic: 't.c' - 24 | wire [31:0] c; +%Warning-UNOPTFLAT: t/t_unopt_combo.v:23:25: Signal unoptimizable: Circular combinational logic: 't.b' + 23 | wire [31:0] b; | ^ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. - t/t_unopt_combo.v:24:25: Example path: t.c - t/t_unopt_combo.v:81:4: Example path: ALWAYS t/t_unopt_combo.v:23:25: Example path: t.b t/t_unopt_combo.v:124:4: Example path: ALWAYS t/t_unopt_combo.v:24:25: Example path: t.c + t/t_unopt_combo.v:81:4: Example path: ALWAYS + t/t_unopt_combo.v:23:25: Example path: t.b %Error: Exiting due to diff --git a/test_regress/t/t_unopt_converge_initial_run_bad.out b/test_regress/t/t_unopt_converge_initial_run_bad.out index a410304f6..8ea2e3603 100644 --- a/test_regress/t/t_unopt_converge_initial_run_bad.out +++ b/test_regress/t/t_unopt_converge_initial_run_bad.out @@ -1,5 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_unopt_converge_initial_run_bad___024root___change_request --V{t#,#} CHANGE: t/t_unopt_converge_initial.v:19: x -%Error: t/t_unopt_converge_initial.v:7: Verilated model didn't DC converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x) +%Error: t/t_unopt_converge_initial.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_ndbg_bad.out b/test_regress/t/t_unopt_converge_ndbg_bad.out index c54c40377..d3d5894d8 100644 --- a/test_regress/t/t_unopt_converge_ndbg_bad.out +++ b/test_regress/t/t_unopt_converge_ndbg_bad.out @@ -1,2 +1,2 @@ -%Error: t/t_unopt_converge.v:7: Verilated model didn't converge +%Error: t/t_unopt_converge.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_print_bad.out b/test_regress/t/t_unopt_converge_print_bad.out index 800401d79..ba694a255 100644 --- a/test_regress/t/t_unopt_converge_print_bad.out +++ b/test_regress/t/t_unopt_converge_print_bad.out @@ -1,5 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_unopt_converge_print_bad___024root___change_request --V{t#,#} CHANGE: t/t_unopt_converge.v:19: x -%Error: t/t_unopt_converge.v:7: Verilated model didn't converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x) +%Error: t/t_unopt_converge.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_run_bad.out b/test_regress/t/t_unopt_converge_run_bad.out index 123a3e718..ba694a255 100644 --- a/test_regress/t/t_unopt_converge_run_bad.out +++ b/test_regress/t/t_unopt_converge_run_bad.out @@ -1,5 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_unopt_converge_run_bad___024root___change_request --V{t#,#} CHANGE: t/t_unopt_converge.v:19: x -%Error: t/t_unopt_converge.v:7: Verilated model didn't converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x) +%Error: t/t_unopt_converge.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_unopt_bad.out b/test_regress/t/t_unopt_converge_unopt_bad.out index 41c20b620..7aa0e9039 100644 --- a/test_regress/t/t_unopt_converge_unopt_bad.out +++ b/test_regress/t/t_unopt_converge_unopt_bad.out @@ -1,9 +1,9 @@ -%Warning-UNOPT: t/t_unopt_converge.v:19:11: Signal unoptimizable: Feedback to public clock or circular logic: 'x' +%Warning-UNOPTFLAT: t/t_unopt_converge.v:19:11: Signal unoptimizable: Circular combinational logic: 'x' 19 | output x; | ^ - ... For warning description see https://verilator.org/warn/UNOPT?v=latest - ... Use "/* verilator lint_off UNOPT */" and lint_on around source to disable this message. - t/t_unopt_converge.v:19:11: Example path: x - t/t_unopt_converge.v:22:4: Example path: ALWAYS - t/t_unopt_converge.v:19:11: Example path: x + ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest + ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. + t/t_unopt_converge.v:19:11: Example path: x + t/t_unopt_converge.v:23:9: Example path: ASSIGNW + t/t_unopt_converge.v:19:11: Example path: x %Error: Exiting due to diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 15d38a892..6ec899e69 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -1,4 +1,4 @@ -%Warning-UNOPTFLAT: t/t_unoptflat_simple_2.v:15:15: Signal unoptimizable: Feedback to clock or circular logic: 't.x' +%Warning-UNOPTFLAT: t/t_unoptflat_simple_2.v:15:15: Signal unoptimizable: Circular combinational logic: 't.x' 15 | wire [2:0] x; | ^ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest @@ -6,9 +6,9 @@ t/t_unoptflat_simple_2.v:15:15: Example path: t.x t/t_unoptflat_simple_2.v:17:18: Example path: ASSIGNW t/t_unoptflat_simple_2.v:15:15: Example path: t.x - ... Widest candidate vars to split: - t/t_unoptflat_simple_2.v:15:15: t.x, width 3, fanout 10, can split_var - ... Most fanned out candidate vars to split: - t/t_unoptflat_simple_2.v:15:15: t.x, width 3, fanout 10, can split_var + ... Widest variables candidate to splitting: + t/t_unoptflat_simple_2.v:15:15: t.x, width 3, circular fanout 2, can split_var + ... Candidates with the highest fanout: + t/t_unoptflat_simple_2.v:15:15: t.x, width 3, circular fanout 2, can split_var ... Suggest add /*verilator split_var*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_var_escape.out b/test_regress/t/t_var_escape.out index a4bcf2e13..3cf2f0549 100644 --- a/test_regress/t/t_var_escape.out +++ b/test_regress/t/t_var_escape.out @@ -1,32 +1,31 @@ $version Generated by VerilatedVcd $end -$date Tue Jul 24 18:44:43 2012 - $end +$date Thu Sep 22 13:02:07 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 * 9num $end - $var wire 1 + bra[ket]slash/dash-colon:9backslash\done $end - $var wire 1 ' clk $end - $var wire 1 ) double__underscore $end - $var wire 1 ( escaped_normal $end + $var wire 1 & 9num $end + $var wire 1 ' bra[ket]slash/dash-colon:9backslash\done $end + $var wire 1 # clk $end + $var wire 1 % double__underscore $end + $var wire 1 $ escaped_normal $end $scope module t $end - $var wire 1 * 9num $end - $var wire 32 & a0.cyc [31:0] $end - $var wire 1 + bra[ket]slash/dash-colon:9backslash\done $end + $var wire 1 $ 9num $end + $var wire 32 * a0.cyc [31:0] $end + $var wire 1 $ bra[ket]slash/dash-colon:9backslash\done $end $var wire 1 $ check:alias $end - $var wire 1 % check;alias $end + $var wire 1 ) check;alias $end $var wire 1 $ check_alias $end - $var wire 1 ' clk $end - $var wire 32 # cyc [31:0] $end - $var wire 1 ) double__underscore $end - $var wire 1 ( escaped_normal $end - $var wire 32 & other.cyc [31:0] $end + $var wire 1 # clk $end + $var wire 32 ( cyc [31:0] $end + $var wire 1 $ double__underscore $end + $var wire 1 $ escaped_normal $end + $var wire 32 * other.cyc [31:0] $end $var wire 1 $ wire $end $scope module a0 $end - $var wire 32 # cyc [31:0] $end + $var wire 32 ( cyc [31:0] $end $upscope $end $scope module mod.with_dot $end - $var wire 32 # cyc [31:0] $end + $var wire 32 ( cyc [31:0] $end $upscope $end $upscope $end $upscope $end @@ -34,130 +33,119 @@ $enddefinitions $end #0 +0# 1$ -0% -b11111111111111111111111111111110 & -b00000000000000000000000000000001 # -0' -1( -1) -1* -1+ +1% +1& +1' +b00000000000000000000000000000001 ( +0) +b11111111111111111111111111111110 * #10 +1# 0$ -1% -b11111111111111111111111111111101 & -b00000000000000000000000000000010 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000010 ( +1) +b11111111111111111111111111111101 * #15 -0' +0# #20 +1# 1$ -0% -b11111111111111111111111111111100 & -b00000000000000000000000000000011 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000011 ( +0) +b11111111111111111111111111111100 * #25 -0' +0# #30 +1# 0$ -1% -b11111111111111111111111111111011 & -b00000000000000000000000000000100 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000100 ( +1) +b11111111111111111111111111111011 * #35 -0' +0# #40 +1# 1$ -0% -b11111111111111111111111111111010 & -b00000000000000000000000000000101 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000101 ( +0) +b11111111111111111111111111111010 * #45 -0' +0# #50 +1# 0$ -1% -b11111111111111111111111111111001 & -b00000000000000000000000000000110 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000110 ( +1) +b11111111111111111111111111111001 * #55 -0' +0# #60 +1# 1$ -0% -b11111111111111111111111111111000 & -b00000000000000000000000000000111 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000111 ( +0) +b11111111111111111111111111111000 * #65 -0' +0# #70 +1# 0$ -1% -b11111111111111111111111111110111 & -b00000000000000000000000000001000 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000001000 ( +1) +b11111111111111111111111111110111 * #75 -0' +0# #80 +1# 1$ -0% -b11111111111111111111111111110110 & -b00000000000000000000000000001001 # -1' -1( -1) -1* -1+ -#85 -0' -#90 -0$ 1% -b11111111111111111111111111110101 & -b00000000000000000000000000001010 # +1& 1' -0( +b00000000000000000000000000001001 ( 0) -0* -0+ -#95 -0' -#100 -1$ +b11111111111111111111111111110110 * +#85 +0# +#90 +1# +0$ 0% -b11111111111111111111111111110100 & -b00000000000000000000000000001011 # -1' -1( +0& +0' +b00000000000000000000000000001010 ( 1) -1* -1+ +b11111111111111111111111111110101 * +#95 +0# +#100 +1# +1$ +1% +1& +1' +b00000000000000000000000000001011 ( +0) +b11111111111111111111111111110100 * diff --git a/test_regress/t/t_var_pinsizes.v b/test_regress/t/t_var_pinsizes.v index d0e9e26bc..335f0b097 100644 --- a/test_regress/t/t_var_pinsizes.v +++ b/test_regress/t/t_var_pinsizes.v @@ -5,7 +5,6 @@ // SPDX-License-Identifier: CC0-1.0 // Also check that SystemC is ordering properly -// verilator lint_on IMPERFECTSCH module t (/*AUTOARG*/ // Outputs diff --git a/test_regress/t/t_verilated_all.pl b/test_regress/t/t_verilated_all.pl index 402f834d4..4cdd44194 100755 --- a/test_regress/t/t_verilated_all.pl +++ b/test_regress/t/t_verilated_all.pl @@ -18,6 +18,7 @@ compile( "--coverage-toggle --coverage-line --coverage-user", "--trace --vpi ", "--trace-threads 1", + $Self->have_coroutines ? "--timing" : "--no-timing -Wno-STMTDLY", "--prof-exec", "--prof-pgo", "$root/include/verilated_save.cpp"], threads => 2 @@ -56,6 +57,7 @@ foreach my $file (sort keys %hit) { && $file !~ /_sc/ && $file !~ /_fst/ && $file !~ /_heavy/ + && ($file !~ /_timing/ || $Self->have_coroutines) && ($file !~ /_thread/)) { error("Include file not covered by t_verilated_all test: ", $file); } diff --git a/test_regress/t/t_verilated_all.v b/test_regress/t/t_verilated_all.v index 8edabe38a..2030b8ba8 100644 --- a/test_regress/t/t_verilated_all.v +++ b/test_regress/t/t_verilated_all.v @@ -17,7 +17,7 @@ module t (/*AUTOARG*/ cyc <= cyc + 1; if (cyc!=0) begin if (cyc==10) begin - $write("*-* All Finished *-*\n"); + #5 $write("*-* All Finished *-*\n"); $finish; end end diff --git a/test_regress/t/t_verilated_all_newest.pl b/test_regress/t/t_verilated_all_newest.pl index 221f84ad7..7ea15b92d 100755 --- a/test_regress/t/t_verilated_all_newest.pl +++ b/test_regress/t/t_verilated_all_newest.pl @@ -16,7 +16,8 @@ my $root = ".."; compile( # Can't use --coverage and --savable together, so cheat and compile inline - verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --prof-exec --prof-pgo --vpi $root/include/verilated_save.cpp"], + verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --prof-exec --prof-pgo --vpi $root/include/verilated_save.cpp", + $Self->have_coroutines ? "--timing" : "--no-timing -Wno-STMTDLY"], make_flags => 'DRIVER_STD=newest', ); diff --git a/test_regress/t/t_verilated_debug.out b/test_regress/t/t_verilated_debug.out index 9cbfc96c8..4227ea526 100644 --- a/test_regress/t/t_verilated_debug.out +++ b/test_regress/t/t_verilated_debug.out @@ -7,18 +7,32 @@ internalsDump: -V{t#,#}+++++TOP Evaluate Vt_verilated_debug::eval_step -V{t#,#}+ Vt_verilated_debug___024root___eval_debug_assertions +-V{t#,#}+ Initial +-V{t#,#}+ Vt_verilated_debug___024root___eval_static -V{t#,#}+ Vt_verilated_debug___024root___eval_initial --V{t#,#}+ Vt_verilated_debug___024root___initial__TOP__0 +-V{t#,#}+ Vt_verilated_debug___024root___eval_initial__TOP Data: w96: 000000aa 000000bb 000000cc --V{t#,#}+ Initial loop -V{t#,#}+ Vt_verilated_debug___024root___eval_settle +-V{t#,#}+ Eval -V{t#,#}+ Vt_verilated_debug___024root___eval --V{t#,#}+ Clock loop --V{t#,#}+ Vt_verilated_debug___024root___eval +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} No triggers active -V{t#,#}+++++TOP Evaluate Vt_verilated_debug::eval_step -V{t#,#}+ Vt_verilated_debug___024root___eval_debug_assertions --V{t#,#}+ Clock loop +-V{t#,#}+ Eval -V{t#,#}+ Vt_verilated_debug___024root___eval --V{t#,#}+ Vt_verilated_debug___024root___sequent__TOP__0 +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @(posedge clk) +-V{t#,#}+ Vt_verilated_debug___024root___eval_act +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_verilated_debug___024root___eval_nba +-V{t#,#}+ Vt_verilated_debug___024root___nba_sequent__TOP__0 *-* All Finished *-* --V{t#,#}+ Vt_verilated_debug___024root___final +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_verilated_debug___024root___eval_final diff --git a/test_regress/t/t_verilated_threaded.pl b/test_regress/t/t_verilated_threaded.pl index a11df1cfe..a012b4529 100755 --- a/test_regress/t/t_verilated_threaded.pl +++ b/test_regress/t/t_verilated_threaded.pl @@ -16,7 +16,8 @@ my $root = ".."; compile( # Can't use --coverage and --savable together, so cheat and compile inline - verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --vpi $root/include/verilated_save.cpp"], + verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --vpi $root/include/verilated_save.cpp", + $Self->have_coroutines ? "--timing" : "--no-timing -Wno-STMTDLY"], threads => 1 ); diff --git a/test_regress/t/t_vlt_syntax_bad.out b/test_regress/t/t_vlt_syntax_bad.out index ebe31f2ce..5f5cd457d 100644 --- a/test_regress/t/t_vlt_syntax_bad.out +++ b/test_regress/t/t_vlt_syntax_bad.out @@ -19,4 +19,10 @@ %Error: t/t_vlt_syntax_bad.vlt:18:1: Argument -scope only supported for tracing_on/off_off 18 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 | ^~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:20:1: forceable missing -module + 20 | forceable -module "" -var "net_*" + | ^~~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:22:1: missing -var + 22 | forceable -module "top" -var "" + | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.vlt b/test_regress/t/t_vlt_syntax_bad.vlt index 7f1d2d092..129141465 100644 --- a/test_regress/t/t_vlt_syntax_bad.vlt +++ b/test_regress/t/t_vlt_syntax_bad.vlt @@ -16,3 +16,7 @@ lint_off --rule UNOPTFLAT -scope "top*" lint_off --rule UNOPTFLAT -scope "top*" -levels 0 lint_on --rule UNOPTFLAT -scope "top*" lint_on --rule UNOPTFLAT -scope "top*" -levels 0 +// bad, --module missing +forceable -module "" -var "net_*" +// bad, --var missing +forceable -module "top" -var "" diff --git a/test_regress/t/t_vlt_timing.pl b/test_regress/t/t_vlt_timing.pl new file mode 100755 index 000000000..0b172f726 --- /dev/null +++ b/test_regress/t/t_vlt_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_timing_off.v"); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing t/t_vlt_timing.vlt"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_vlt_timing.vlt b/test_regress/t/t_vlt_timing.vlt new file mode 100644 index 000000000..f5ed20a92 --- /dev/null +++ b/test_regress/t/t_vlt_timing.vlt @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +timing_on --file "t/t_timing_off.v" --lines 23 +timing_off -file "t/t_timing_off.v" -lines 26-34 +timing_on -file "t/t_timing_off.v" -lines 35-38 diff --git a/test_regress/t/t_wait.out b/test_regress/t/t_wait.out index 409170e8e..2de31cc23 100644 --- a/test_regress/t/t_wait.out +++ b/test_regress/t/t_wait.out @@ -1,14 +1,39 @@ -%Error-UNSUPPORTED: t/t_wait.v:12:7: Unsupported: wait statements +%Error-NOTIMING: t/t_wait.v:12:7: Wait statements require --timing + : ... In instance t 12 | wait (value == 1); | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_wait.v:14:7: Unsupported: wait statements + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error-NOTIMING: t/t_wait.v:14:7: Wait statements require --timing + : ... In instance t 14 | wait (0); | ^~~~ -%Error-UNSUPPORTED: t/t_wait.v:17:7: Unsupported: wait statements +%Error-NOTIMING: t/t_wait.v:17:7: Wait statements require --timing + : ... In instance t 17 | wait (value == 2); | ^~~~ -%Error-UNSUPPORTED: t/t_wait.v:20:7: Unsupported: wait statements +%Error-NOTIMING: t/t_wait.v:20:7: Wait statements require --timing + : ... In instance t 20 | wait (value == 3) if (value != 3) $stop; | ^~~~ +%Warning-STMTDLY: t/t_wait.v:25:7: Ignoring delay on this statement due to --no-timing + : ... In instance t + 25 | #10; + | ^ + ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. +%Warning-STMTDLY: t/t_wait.v:27:7: Ignoring delay on this statement due to --no-timing + : ... In instance t + 27 | #10; + | ^ +%Warning-STMTDLY: t/t_wait.v:29:7: Ignoring delay on this statement due to --no-timing + : ... In instance t + 29 | #10; + | ^ +%Warning-STMTDLY: t/t_wait.v:31:7: Ignoring delay on this statement due to --no-timing + : ... In instance t + 31 | #10; + | ^ +%Warning-STMTDLY: t/t_wait.v:33:7: Ignoring delay on this statement due to --no-timing + : ... In instance t + 33 | #10; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_wait.pl b/test_regress/t/t_wait.pl index 89ffd046b..c586a8d47 100755 --- a/test_regress/t/t_wait.pl +++ b/test_regress/t/t_wait.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(linter => 1); lint( - verilator_flags2 => ['--lint-only'], + verilator_flags2 => ['--lint-only --no-timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_wait_timing.pl b/test_regress/t/t_wait_timing.pl new file mode 100755 index 000000000..8c852569f --- /dev/null +++ b/test_regress/t/t_wait_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_wait.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timing -Wno-WAITCONST"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_waiveroutput.out b/test_regress/t/t_waiveroutput.out index 01132a95d..e0b0f6767 100644 --- a/test_regress/t/t_waiveroutput.out +++ b/test_regress/t/t_waiveroutput.out @@ -9,5 +9,5 @@ // lint_off -rule WIDTH -file "*t/t_waiveroutput.v" -match "Operator ASSIGN expects 1 bits on the Assign RHS, but Assign RHS's CONST '2'h3' generates 2 bits." -// lint_off -rule UNUSED -file "*t/t_waiveroutput.v" -match "Signal is not used: 'width_warn'" +// lint_off -rule UNUSEDSIGNAL -file "*t/t_waiveroutput.v" -match "Signal is not used: 'width_warn'" diff --git a/test_regress/t/t_wrapper_context_top0.out b/test_regress/t/t_wrapper_context_top0.out index 4affa5cef..20ca811c4 100644 --- a/test_regress/t/t_wrapper_context_top0.out +++ b/test_regress/t/t_wrapper_context_top0.out @@ -73,7 +73,7 @@ C 'ft/t_wrapper_context.vl33n7pagev_branch/topoifS33-34htop0.top' C 'ft/t_wrapper_context.vl33n8pagev_branch/topoelseS36htop0.top' 10 C 'ft/t_wrapper_context.vl38n4pagev_line/topoblockS38-39htop0.top' 1 C 'ft/t_wrapper_context.vl40n7pagev_branch/topoifS40htop0.top' 0 -C 'ft/t_wrapper_context.vl40n8pagev_branch/topoelseS46htop0.top' 24 +C 'ft/t_wrapper_context.vl40n8pagev_branch/topoelseS46htop0.top' 23 C 'ft/t_wrapper_context.vl41n11pagev_line/topoelsehtop0.top' 0 C 'ft/t_wrapper_context.vl47n10pagev_branch/topoifS47-49htop0.top' 1 -C 'ft/t_wrapper_context.vl47n11pagev_branch/topoelsehtop0.top' 23 +C 'ft/t_wrapper_context.vl47n11pagev_branch/topoelsehtop0.top' 33 diff --git a/test_regress/t/t_wrapper_context_top1.out b/test_regress/t/t_wrapper_context_top1.out index 84013beb4..889121356 100644 --- a/test_regress/t/t_wrapper_context_top1.out +++ b/test_regress/t/t_wrapper_context_top1.out @@ -72,8 +72,8 @@ C 'ft/t_wrapper_context.vl32n4pagev_line/topoblockS32htop1.top' 6 C 'ft/t_wrapper_context.vl33n7pagev_branch/topoifS33-34htop1.top' 1 C 'ft/t_wrapper_context.vl33n8pagev_branch/topoelseS36htop1.top' 5 C 'ft/t_wrapper_context.vl38n4pagev_line/topoblockS38-39htop1.top' 1 -C 'ft/t_wrapper_context.vl40n7pagev_branch/topoifS40htop1.top' 14 +C 'ft/t_wrapper_context.vl40n7pagev_branch/topoifS40htop1.top' 13 C 'ft/t_wrapper_context.vl40n8pagev_branch/topoelseS46htop1.top' 0 -C 'ft/t_wrapper_context.vl41n11pagev_line/topoelsehtop1.top' 13 +C 'ft/t_wrapper_context.vl41n11pagev_line/topoelsehtop1.top' 18 C 'ft/t_wrapper_context.vl47n10pagev_branch/topoifS47-49htop1.top' 0 C 'ft/t_wrapper_context.vl47n11pagev_branch/topoelsehtop1.top' 0 diff --git a/test_regress/t/t_xml_debugcheck.out b/test_regress/t/t_xml_debugcheck.out index 839942936..35a4d21a9 100644 --- a/test_regress/t/t_xml_debugcheck.out +++ b/test_regress/t/t_xml_debugcheck.out @@ -22,860 +22,62 @@ - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + - + - - - + + + + + + + + + + + + + + + - - + + - - - - + + + + - - + + - - - - + + + + @@ -886,17 +88,17 @@ - - + + - - - + + + - - - - + + + + @@ -905,17 +107,17 @@ - - + + - - - + + + - - - - + + + + @@ -928,17 +130,17 @@ - - + + - - - + + + - - - - + + + + @@ -947,17 +149,17 @@ - - + + - - - + + + - - - - + + + + @@ -970,21 +172,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -995,21 +197,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1024,21 +226,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1049,21 +251,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1078,21 +280,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1103,21 +305,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1132,26 +334,26 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + @@ -1162,26 +364,26 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + @@ -1192,17 +394,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1211,17 +413,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1234,17 +436,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1253,17 +455,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1276,150 +478,1161 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - - + + - - - + + + - - - + + + - - - + + + - - - - - - - - - + + + + + + + + + - + - - + + - - - - + + + + - + - - - + + + - - - - + + + + - + - - + + - - + + - - + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + @@ -1433,11 +1646,20 @@ - + + + + + + + + + + @@ -1445,39 +1667,39 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -1487,10 +1709,10 @@ - + - + @@ -1509,46 +1731,50 @@ - + - - - + + + - - + + - - + + - - - + + + - - + + - + - - + + - + - - + + - + - - - - + + + + + + + + diff --git a/test_regress/t/t_xml_first.out b/test_regress/t/t_xml_first.out index 11cff436a..1056865dc 100644 --- a/test_regress/t/t_xml_first.out +++ b/test_regress/t/t_xml_first.out @@ -50,8 +50,8 @@ - - + + diff --git a/test_regress/t/t_xml_flat.out b/test_regress/t/t_xml_flat.out index bc2e4f9b8..d7ec257c2 100644 --- a/test_regress/t/t_xml_flat.out +++ b/test_regress/t/t_xml_flat.out @@ -99,8 +99,8 @@ - - + + diff --git a/verilator-config.cmake.in b/verilator-config.cmake.in index 2125341b8..2210b3297 100644 --- a/verilator-config.cmake.in +++ b/verilator-config.cmake.in @@ -89,6 +89,12 @@ define_property(TARGET FULL_DOCS "Verilator multithread tracing enabled" ) +define_property(TARGET + PROPERTY VERILATOR_TIMING + BRIEF_DOCS "Verilator timing enabled" + FULL_DOCS "Verilator timing enabled" +) + define_property(TARGET PROPERTY VERILATOR_COVERAGE BRIEF_DOCS "Verilator coverage enabled" @@ -337,6 +343,11 @@ function(verilate TARGET) endif() target_compile_features(${TARGET} PRIVATE cxx_std_11) + + if (${VERILATE_PREFIX}_TIMING) + check_cxx_compiler_flag(-fcoroutines-ts COROUTINES_TS_FLAG) + target_compile_options(${TARGET} PRIVATE $,-fcoroutines-ts,-fcoroutines>) + endif() endfunction() function(_verilator_find_systemc)