Merge from master for release.

This commit is contained in:
Wilson Snyder 2022-10-29 17:52:23 -04:00
commit db39d70c75
678 changed files with 30884 additions and 7966 deletions

View File

@ -56,7 +56,6 @@ DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH

View File

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

View File

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

View File

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

View File

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

84
Changes
View File

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

View File

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

View File

@ -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 <filename> Override build dependency Verilator binary
--build-jobs <jobs> Parallelism for --build
@ -309,10 +311,12 @@ detailed descriptions of these arguments.
+define+<var>=<value> 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-<srcfile> Enable dumping everything in source file
--dumpi-dfg <level> Enable dumping DfgGraphs to .dot files at level
--dumpi-graph <level> Enable dumping V3Graphs to .dot files at level
--dumpi-tree <level> Enable dumping Ast .tree files at level
--dumpi-<srcfile> <level> 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 <var> Get environment variable with defaults
--get-supported <feature> Get if feature is supported
--help Display this help
--hierarchical Enable hierarchical Verilation
-I<dir> Directory to search for includes
@ -398,6 +403,8 @@ detailed descriptions of these arguments.
--threads <threads> Enable multithreading
--threads-dpi <mode> Enable multithreaded DPI
--threads-max-mtasks <mtasks> Tune maximum mtask partitioning
--timing Enable timing support
--no-timing Disable timing support
--timescale <timescale> Sets default timescale
--timescale-override <timescale> Overrides all timescales
--top <topname> Alias of --top-module

View File

@ -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 <coroutine>
],[[]])],
[_my_result=yes
AC_DEFINE([HAVE_COROUTINES],[1],[Defined if coroutines are supported by $CXX])],
[AC_LINK_IFELSE(
[AC_LANG_PROGRAM([#include <experimental/coroutine>],[[]])],
[_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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
<https://chipsalliance.org>`_ and `Shunyao CAD <https://shunyaocad.com>`_.
<https://chipsalliance.org>`_, `Antmicro Ltd <https://antmicro.com>`_ and
`Shunyao CAD <https://shunyaocad.com>`_.
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
<https://chipsalliance.org>`_.
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -72,7 +72,7 @@ Summary:
.. option:: +verilator+prof+threads+window+<value>
Deprecated. Alias for :vlopt:`+verilator+prof+exec+window+\<filename\>`
Deprecated. Alias for :vlopt:`+verilator+prof+exec+window+\<value\>`
.. option:: +verilator+prof+vlt+file+<filename>

View File

@ -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 <signal-name>
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 <signal-name>
@ -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 <level>
Rarely needed - for developer use. Set internal DfgGraph dumping level
globally to the specified value.
.. option:: --dumpi-graph <level>
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-<srcfile>`. Level 9 enables dumping of everything.
`--dumpi-V3Order 9`). Level 0 disables dumps and is equivalent to
`--no-dump-<srcfile>`. 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-<pattern>
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 <feature>
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+<ext>[+<ext>][...]
@ -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 <value>
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 <topname>
.. option:: --top-module <topname>
@ -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-<message>
@ -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 "<modulename>" -var "<signame>"
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 "<filename>" [-lines <line> [ - <line>]]]
.. option:: timing_off [-file "<filename>" [-lines <line> [ - <line>]]]
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 "<filename>" [-lines <line> [ - <line> ]]]
.. option:: tracing_off [-file "<filename>" [-lines <line> [ - <line> ]]]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
<locals and inits required for trigger eval>
while (!__Vtrigger) {
co_await __VdynSched.evaluation();
<pre updates>;
__Vtrigger = <trigger eval>;
[optionally] co_await __VdynSched.postUpdate();
<post updates>;
}
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 ``<identifier>``. For list type children, the getter is ``<identifier>``,
and instead of the setter, there an ``add<Identifier>`` method is generated
that appends new nodes (or lists of nodes) to the child list.
``alias op<N>`` 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
<https://www.graphviz.org>`__ 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
------------------

View File

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

View File

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

View File

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

6
examples/make_hello_binary/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.dmp
*.log
*.csrc
*.vcd
obj_*
logs

View File

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

View File

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

View File

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

View File

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

View File

@ -140,7 +140,7 @@ void **JenkinsIns(void *base_i, const unsigned char *mem, uint32_t length, uint3
#include <sys/sysctl.h>
#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

View File

@ -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<char>(
'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<char>(
'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();
}
}
//===========================================================================

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {};
}

429
include/verilated_timing.h Normal file
View File

@ -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 <experimental/coroutine>
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 <coroutine>
# 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 "<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<VlDelayedCoroutine>;
// 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<VlCoroutineHandle>;
// 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;
// <locals and inits required for trigger eval>
// while (!__Vtrigger) {
// co_await __VdynSched.evaluation();
// <pre updates>;
// __Vtrigger = <trigger eval>;
// [optionally] co_await __VdynSched.postUpdate();
// <post updates>;
// }
// 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<VlCoroutineHandle>;
// 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<VlJoin> 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<VlJoin> 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

View File

@ -644,6 +644,10 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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);
}

View File

@ -29,6 +29,8 @@
#endif
#include <algorithm>
#include <array>
#include <atomic>
#include <deque>
#include <map>
#include <set>
@ -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<words> name ///< Declare output signal, 65+ bits
//===================================================================
// Activity trigger vector
template <std::size_t T_size> //
class VlTriggerVec final {
// TODO: static assert T_size > 0, and don't generate when empty
private:
// MEMBERS
std::array<bool, T_size> 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<T_size>& 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<T_size>& a, const VlTriggerVec<T_size>& 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<T_Words>& 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<T_Value, T_Depth>& that) const { return neq(*this, that); }
// Similar to 'neq' above, *this = that used for change detection
void assign(const VlUnpacked<T_Value, T_Depth>& 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 <typename T_Val, std::size_t T_Dep>
static bool neq(const VlUnpacked<T_Val, T_Dep>& a, const VlUnpacked<T_Val, T_Dep>& 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 <typename T_Other> //
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 <class T_Value, std::size_t T_Depth>
@ -908,12 +1004,179 @@ std::string VL_TO_STRING(const VlUnpacked<T_Value, T_Depth>& 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<VlClass*> m_newGarbage VL_GUARDED_BY(m_mutex);
// Queue of objects currently being deleted (only for deleteAll())
std::vector<VlClass*> 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 <typename T_Class>
friend class VlClassRef; // Needed for access to the ref counter and deleter
// MEMBERS
std::atomic<size_t> 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 <typename T_Class>
class VlClassRef final {
private:
// TYPES
template <typename T_OtherClass>
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 <typename... T_Args>
VlClassRef(VlDeleter& deleter, T_Args&&... args)
: m_objp{new T_Class{std::forward<T_Args>(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 <typename T_OtherClass>
VlClassRef& operator=(const VlClassRef<T_OtherClass>& copied) {
refCountDec();
m_objp = copied.m_objp;
refCountInc();
return *this;
}
template <typename T_OtherClass>
VlClassRef& operator=(VlClassRef<T_OtherClass>&& 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 <typename T_OtherClass>
VlClassRef<T_OtherClass> dynamicCast() const {
return VlClassRef<T_OtherClass>{dynamic_cast<T_OtherClass*>(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<Class> { vlSymsp->__Vm_deleter, __VA_ARGS__ }
#define VL_KEEP_THIS \
VlClassRef<std::remove_pointer<decltype(this)>::type> __Vthisref { this }
template <class T> // T typically of type VlClassRef<x>
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 <typename T, typename U>
static inline bool VL_CAST_DYNAMIC(VlClassRef<T> in, VlClassRef<U>& outr) {
VlClassRef<U> casted = std::dynamic_pointer_cast<U>(in);
VlClassRef<U> casted = in.template dynamicCast<U>();
if (VL_LIKELY(casted)) {
outr = casted;
return true;

View File

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

View File

@ -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 <utility>
namespace vlstd {
template <typename T>
@ -564,6 +574,14 @@ T const& as_const(T& v) {
return v;
}
// C++14's std::exchange
template <class T, class U = T>
T exchange(T& obj, U&& new_value) {
T old_value = std::move(obj);
obj = std::forward<U>(new_value);
return old_value;
}
}; // namespace vlstd
//=========================================================================

View File

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

View File

@ -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<VNRef<AstSenTree>, 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 <typename SenItemKind>
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 <typename SenItemKind>
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 <typename SenItemKind>
AstActive* getSpecialActive(FileLine* fl) {
AstActive*& cachep = getSpecialActive<SenItemKind>();
if (!cachep) cachep = makeSpecialActive<SenItemKind>(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<AstSenItem::Static>() {
return m_sActivep;
}
template <>
AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Initial>() {
return m_iActivep;
}
template <>
AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Final>() {
return m_fActivep;
}
template <>
AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Combo>() {
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<AstVarRef>([&](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 <typename T>
void moveUnderSpecial(AstNode* nodep) {
AstActive* const wantactivep = m_namer.getSpecialActive<T>(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<AstSenItem::Combo>(nodep->fileline())
: oldsensesp ? m_namer.getActive(nodep->fileline(), oldsensesp)
: m_namer.getSpecialActive<AstSenItem::Initial>(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<AstSenItem::Static>(nodep); }
void visit(AstInitial* nodep) override {
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
visitSenItems(nodep);
moveUnderSpecial<AstSenItem::Initial>(nodep);
}
void visit(AstFinal* nodep) override {
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
moveUnderSpecial<AstSenItem::Final>(nodep);
}
void visit(AstAssignAlias* nodep) override { moveUnderSpecial<AstSenItem::Combo>(nodep); }
void visit(AstCoverToggle* nodep) override { moveUnderSpecial<AstSenItem::Combo>(nodep); }
void visit(AstAssignW* nodep) override { moveUnderSpecial<AstSenItem::Combo>(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<AstSenItem::Combo>(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

View File

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

View File

@ -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<AstNode*>(
new AstCMath(fl, "vlSymsp->_vm_contextp__->assertOn()", 1))
: static_cast<AstNode*>(new AstConst(fl, AstConst::BitFalse())))),
nodep);
new AstCMath{fl, "vlSymsp->_vm_contextp__->assertOn()", 1})
: static_cast<AstNode*>(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<AstNode*>(new AstOneHot0(nodep->fileline(), propp))
: static_cast<AstNode*>(new AstOneHot(nodep->fileline(), propp)));
? static_cast<AstNode*>(new AstOneHot0{nodep->fileline(), propp})
: static_cast<AstNode*>(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<AstNode*>(new AstOneHot0(nodep->fileline(), propp))
: static_cast<AstNode*>(new AstOneHot(nodep->fileline(), propp)));
AstIf* const ifp = new AstIf(
nodep->fileline(), new AstLogNot(nodep->fileline(), ohot),
? static_cast<AstNode*>(new AstOneHot0{nodep->fileline(), propp})
: static_cast<AstNode*>(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);
}

View File

@ -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<std::ofstream> 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 {

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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<uint32_t, uint32_t> 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<const AstDefImplicitDType*>(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<std::string, std::string>); }
@ -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<const AstUnpackArrayDType*>(samep);
@ -1231,7 +1265,7 @@ public:
// Outer dimension comes first. The first element is this node.
std::vector<AstUnpackArrayDType*> 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"; }
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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<int>(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<const void*>(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<const AstVarRef*>(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;
}

View File

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

View File

@ -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<AstNode>([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");

View File

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

View File

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

View File

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

View File

@ -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 <algorithm>
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<AstVarScope>([&state](AstVarScope* vscp) {
if (vscp->isCircular()) {
vscp->v3warn(IMPERFECTSCH,
"Imperfect scheduling of variable: " << vscp->prettyNameQ());
ChangedInsertVisitor{vscp, state};
}
});
V3Global::dumpCheckGlobalTree("changed", 0, dumpTree() >= 3);
}

View File

@ -34,6 +34,7 @@
#include "V3Ast.h"
#include "V3Global.h"
#include "V3Sched.h"
#include <algorithm>
@ -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<AstVarScope*>(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;
};

View File

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

View File

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

View File

@ -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<AstVarRef>([](const AstVarRef* nodep) {
nodep->lhsp()->foreach([](const AstVarRef* nodep) {
if (nodep->varp()) nodep->varp()->user4(1);
});
nodep->rhsp()->foreach<AstVarRef>([&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);

View File

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

View File

@ -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<AstCell>([](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);
}

View File

@ -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<AstSenTree*> 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<const std::pair<AstNodeModule*, std::string>, 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<AstNode*> 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);

View File

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

466
src/V3Dfg.cpp Normal file
View File

@ -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<DfgVertex*>& src, V3List<DfgVertex*>& 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<DfgVarPacked>()) {
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<DfgVarArray>()) {
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<DfgConst>()) {
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<DfgSel>()) {
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<DfgVertexVar>()) 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<std::ofstream> 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<const DfgVertex*> queue{&vtx};
// Set of already visited vertices
std::unordered_set<const DfgVertex*> 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<DfgVarPacked>()) {
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<std::ofstream> 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<DfgVarPacked>([](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<std::ofstream> 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<V3Hash>();
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<DfgVertexVar>()) {
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<DfgConst>()->num());
}
V3Hash DfgConst::selfHash() const { return num().toHash(); }
// DfgSel ----------
bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as<DfgSel>()->lsb(); }
V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
// DfgVertexVar ----------
bool DfgVertexVar::selfEquals(const DfgVertex& that) const {
UASSERT_OBJ(varp() != that.as<DfgVertexVar>()->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

901
src/V3Dfg.h Normal file
View File

@ -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 <algorithm>
#include <array>
#include <functional>
#include <new>
#include <type_traits>
#include <unordered_map>
#include <vector>
#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<const DfgVertex*, const DfgVertex*> for use below
template <>
struct std::hash<std::pair<const DfgVertex*, const DfgVertex*>> final {
size_t operator()(const std::pair<const DfgVertex*, const DfgVertex*>& item) const {
const size_t a = reinterpret_cast<std::uintptr_t>(item.first);
const size_t b = reinterpret_cast<std::uintptr_t>(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<DfgVertex*> m_varVertices; // The variable vertices in the graph
V3List<DfgVertex*> m_constVertices; // The constant vertices in the graph
V3List<DfgVertex*> 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<void(DfgVertex&)> f);
// 'const' variant of 'forEachVertex'. No mutation allowed.
inline void forEachVertex(std::function<void(const DfgVertex&)> 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<std::unique_ptr<DfgGraph>> 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<std::unique_ptr<DfgGraph>> 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<DfgVertex*&>(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<DfgVertex*> 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 <typename T>
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<T*>(&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 <typename T>
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<T*>(&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<std::pair<const DfgVertex*, const DfgVertex*>, 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<DfgEdge*, size_t> sourceEdges() = 0;
// Source edges of this vertex
virtual std::pair<const DfgEdge*, size_t> 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<void(DfgVertex&)> f);
// Calls given function 'f' for each source vertex of this vertex
// Unconnected source edges are not iterated.
inline void forEachSource(std::function<void(const DfgVertex&)> f) const;
// Calls given function 'f' for each source edge of this vertex. Also passes source index.
inline void forEachSourceEdge(std::function<void(DfgEdge&, size_t)> f);
// Calls given function 'f' for each source edge of this vertex. Also passes source index.
inline void forEachSourceEdge(std::function<void(const DfgEdge&, size_t)> 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<void(DfgVertex&)> f);
// Calls given function 'f' for each sink vertex of this vertex
inline void forEachSink(std::function<void(const DfgVertex&)> 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<void(DfgEdge&)> f);
// Calls given function 'f' for each sink edge of this vertex.
inline void forEachSinkEdge(std::function<void(const DfgEdge&)> 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<bool(const DfgEdge&, size_t)> p) const;
// Returns first sink vertex of type 'Vertex' which satisfies the given predicate 'p',
// or nullptr if no such sink vertex exists
template <typename Vertex>
inline Vertex* findSink(std::function<bool(const Vertex&)> 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 <typename Vertex>
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 <typename T>
inline static bool privateTypeTest(const DfgVertex* nodep);
public:
// Subtype test
template <typename T>
bool is() const {
static_assert(std::is_base_of<DfgVertex, T>::value, "'T' must be a subtype of DfgVertex");
return privateTypeTest<typename std::remove_cv<T>::type>(this);
}
// Ensure subtype, then cast to that type
template <typename T>
T* as() {
UASSERT_OBJ(is<T>(), this,
"DfgVertex is not of expected type, but instead has type '" << typeName()
<< "'");
return static_cast<T*>(this);
}
template <typename T>
const T* as() const {
UASSERT_OBJ(is<T>(), this,
"DfgVertex is not of expected type, but instead has type '" << typeName()
<< "'");
return static_cast<const T*>(this);
}
// Cast to subtype, or null if different
template <typename T>
T* cast() {
return is<T>() ? static_cast<T*>(this) : nullptr;
}
template <typename T>
const T* cast() const {
return is<T>() ? static_cast<const T*>(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<DfgConst>()) {
vtx.m_verticesEnt.pushBack(m_constVertices, &vtx);
} else if (vtx.is<DfgVertexVar>()) {
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<DfgConst>()) {
vtx.m_verticesEnt.unlink(m_constVertices, &vtx);
} else if (vtx.is<DfgVertexVar>()) {
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<void(DfgVertex&)> 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<void(const DfgVertex&)> 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<void(DfgVertex&)> 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<void(const DfgVertex&)> 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<void(DfgVertex&)> f) {
for (const DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) {
nextp = edgep->m_nextp;
f(*edgep->m_sinkp);
}
}
void DfgVertex::forEachSink(std::function<void(const DfgVertex&)> f) const {
for (const DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) f(*edgep->m_sinkp);
}
void DfgVertex::forEachSourceEdge(std::function<void(DfgEdge&, size_t)> 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<void(const DfgEdge&, size_t)> 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<void(DfgEdge&)> f) {
for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) {
nextp = edgep->m_nextp;
f(*edgep);
}
}
void DfgVertex::forEachSinkEdge(std::function<void(const DfgEdge&)> f) const {
for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) {
nextp = edgep->m_nextp;
f(*edgep);
}
}
const DfgEdge* DfgVertex::findSourceEdge(std::function<bool(const DfgEdge&, size_t)> 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 <typename Vertex>
Vertex* DfgVertex::findSink(std::function<bool(const Vertex&)> p) const {
static_assert(std::is_base_of<DfgVertex, Vertex>::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<Vertex>()) {
if (p(*sinkp)) return sinkp;
}
}
return nullptr;
}
template <typename Vertex>
Vertex* DfgVertex::findSink() const {
static_assert(!std::is_same<DfgVertex, Vertex>::value,
"'Vertex' must be proper subclass of 'DfgVertex'");
return findSink<Vertex>([](const Vertex&) { return true; });
}
//------------------------------------------------------------------------------
// DfgVertex sub-types follow
//------------------------------------------------------------------------------
// Include macros generated by 'astgen'. These include DFGGEN_MEMBERS_<Node>
// 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 <size_t Arity>
class DfgVertexWithArity VL_NOT_FINAL : public DfgVertex {
static_assert(1 <= Arity && Arity <= 4, "Arity must be between 1 and 4 inclusive");
std::array<DfgEdge, Arity> m_srcs; // Source edges
protected:
DfgVertexWithArity<Arity>(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<Arity>() override = default;
public:
std::pair<DfgEdge*, size_t> sourceEdges() final override { //
return {m_srcs.data(), Arity};
}
std::pair<const DfgEdge*, size_t> sourceEdges() const final override {
return {m_srcs.data(), Arity};
}
template <size_t Index>
DfgEdge* sourceEdge() {
static_assert(Index < Arity, "Source index out of range");
return &m_srcs[Index];
}
template <size_t Index>
const DfgEdge* sourceEdge() const {
static_assert(Index < Arity, "Source index out of range");
return &m_srcs[Index];
}
template <size_t Index>
DfgVertex* source() const {
static_assert(Index < Arity, "Source index out of range");
return m_srcs[Index].sourcep();
}
template <size_t Index>
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<DfgEdge*, size_t> sourceEdges() override { return {m_srcsp, m_srcCnt}; }
std::pair<const DfgEdge*, size_t> 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<DfgVertexVar*>(m_varVertices.begin());
}
DfgVertexVar* DfgGraph::varVerticesRbeginp() const {
return static_cast<DfgVertexVar*>(m_varVertices.rbegin());
}
DfgConst* DfgGraph::constVerticesBeginp() const {
return static_cast<DfgConst*>(m_constVertices.begin());
}
DfgConst* DfgGraph::constVerticesRbeginp() const {
return static_cast<DfgConst*>(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<DfgConst>()) return constp->isZero();
return false;
}
bool DfgVertex::isOnes() const {
if (const DfgConst* const constp = cast<DfgConst>()) return constp->isOnes();
return false;
}
#endif

520
src/V3DfgAstToDfg.cpp Normal file
View File

@ -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 <typename Vertex, typename Node>
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<DfgCCast, AstCCast>(const AstCCast*, DfgGraph&) {
return nullptr;
}
// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway
template <>
DfgAtoN* makeVertex<DfgAtoN, AstAtoN>(const AstAtoN*, DfgGraph&) {
return nullptr;
}
// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway
template <>
DfgCompareNN* makeVertex<DfgCompareNN, AstCompareNN>(const AstCompareNN*, DfgGraph&) {
return nullptr;
}
// Unhandled in DfgToAst, but also operates on unpacked arrays which we don't optimize anyway
template <>
DfgSliceSel* makeVertex<DfgSliceSel, AstSliceSel>(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<DfgVertex*> 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<DfgVarPacked*> m_varPackedps; // All the DfgVarPacked vertices we created.
std::vector<DfgVarArray*> 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<DfgVertexVar*>();
}
DfgVertex* getVertex(AstNode* nodep) {
DfgVertex* vtxp = nodep->user1u().to<DfgVertex*>();
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<DfgVarPacked>()->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<DfgVarPacked>()->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<DfgVarArray>()->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<Driver> 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<DfgVertex*>());
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<DfgVertex*>());
muxp->lsbp(nodep->lsbp()->user1u().to<DfgVertex*>());
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);
}

544
src/V3DfgDecomposition.cpp Normal file
View File

@ -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 <deque>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
class SplitIntoComponents final {
// STATE
DfgGraph& m_dfg; // The input graph
const std::string m_prefix; // Component name prefix
std::vector<std::unique_ptr<DfgGraph>> 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<DfgVertex*> 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<size_t>()) 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<size_t>()) continue;
// Assign to current component
item.user<size_t>() = 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<size_t>()) {
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<size_t>()
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<std::unique_ptr<DfgGraph>> apply(DfgGraph& dfg, const std::string& label) {
return std::move(SplitIntoComponents{dfg, label}.m_components);
}
};
std::vector<std::unique_ptr<DfgGraph>> DfgGraph::splitIntoComponents(std::string label) {
return SplitIntoComponents::apply(*this, label);
}
class ExtractCyclicComponents final {
static constexpr size_t UNASSIGNED = std::numeric_limits<size_t>::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<VertexState> 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<DfgVertex*> m_stack; // The stack used by the algorithm
//==========================================================================
// State for extraction
// The extracted cyclic components
std::vector<std::unique_ptr<DfgGraph>> m_components;
// Map from 'variable vertex' -> 'component index' -> 'clone in that component'
std::unordered_map<const DfgVertexVar*, std::unordered_map<size_t, DfgVertexVar*>> m_clones;
// METHODS
//==========================================================================
// Shared methods
VertexState& state(DfgVertex& vtx) const { return *vtx.getUser<VertexState*>(); }
VertexState& allocState(DfgVertex& vtx) {
VertexState*& statep = vtx.user<VertexState*>();
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<VertexState*>();
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<DfgVertex>([&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<DfgVertexVar>()) return;
visitMergeSCCs(other, targetComponent);
});
vtx.forEachSink([=](DfgVertex& other) {
if (other.is<DfgVertexVar>()) 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<DfgVarPacked>()) {
clonep = new DfgVarPacked{m_dfg, pVtxp->varp()};
} else if (DfgVarArray* const aVtxp = vtx.cast<DfgVarArray>()) {
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 <typename T_Vertex>
void fixSources(T_Vertex& vtx, std::function<void(T_Vertex&, DfgVertex&, size_t)> relink) {
static_assert(std::is_base_of<DfgVertexVar, T_Vertex>::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<DfgVertexVar>()) 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<T_Vertex>()), 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<DfgVarPacked>()) {
fixSources<DfgVarPacked>(
*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<DfgVarArray>()) {
fixSources<DfgVarArray>( //
*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<DfgVarPacked>()) {
varp->packSources();
if (!varp->hasSinks() && varp->arity() == 0) {
VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
}
continue;
}
if (DfgVarArray* const varp = vtxp->cast<DfgVarArray>()) {
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<DfgVertexVar>()) 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<DfgVertexVar>()) 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<DfgVertexVar>()) {
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<const DfgVertex*> 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<std::unique_ptr<DfgGraph>> apply(DfgGraph& dfg, const std::string& label) {
return std::move(ExtractCyclicComponents{dfg, label}.m_components);
}
};
std::vector<std::unique_ptr<DfgGraph>> DfgGraph::extractCyclicComponents(std::string label) {
return ExtractCyclicComponents::apply(*this, label);
}

453
src/V3DfgDfgToAst.cpp Normal file
View File

@ -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 <algorithm>
#include <unordered_map>
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 <typename Node, typename Vertex, typename... Ops>
Node* makeNode(const Vertex* vtxp, Ops... ops) {
Node* const nodep = new Node{vtxp->fileline(), ops...};
UASSERT_OBJ(nodep->width() == static_cast<int>(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<AstCountOnes, DfgCountOnes, AstNodeMath*>( //
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<AstExtend, DfgExtend, AstNodeMath*>( //
const DfgExtend* vtxp, AstNodeMath* op1) {
return new AstExtend{vtxp->fileline(), op1, static_cast<int>(vtxp->width())};
}
template <>
AstExtendS* makeNode<AstExtendS, DfgExtendS, AstNodeMath*>( //
const DfgExtendS* vtxp, AstNodeMath* op1) {
return new AstExtendS{vtxp->fileline(), op1, static_cast<int>(vtxp->width())};
}
template <>
AstShiftL* makeNode<AstShiftL, DfgShiftL, AstNodeMath*, AstNodeMath*>( //
const DfgShiftL* vtxp, AstNodeMath* op1, AstNodeMath* op2) {
return new AstShiftL{vtxp->fileline(), op1, op2, static_cast<int>(vtxp->width())};
}
template <>
AstShiftR* makeNode<AstShiftR, DfgShiftR, AstNodeMath*, AstNodeMath*>( //
const DfgShiftR* vtxp, AstNodeMath* op1, AstNodeMath* op2) {
return new AstShiftR{vtxp->fileline(), op1, op2, static_cast<int>(vtxp->width())};
}
template <>
AstShiftRS* makeNode<AstShiftRS, DfgShiftRS, AstNodeMath*, AstNodeMath*>( //
const DfgShiftRS* vtxp, AstNodeMath* op1, AstNodeMath* op2) {
return new AstShiftRS{vtxp->fileline(), op1, op2, static_cast<int>(vtxp->width())};
}
//======================================================================
// Currently unhandled nodes - see corresponding AstToDfg functions
// LCOV_EXCL_START
template <>
AstCCast* makeNode<AstCCast, DfgCCast, AstNodeMath*>(const DfgCCast* vtxp, AstNodeMath*) {
vtxp->v3fatalSrc("not implemented");
}
template <>
AstAtoN* makeNode<AstAtoN, DfgAtoN, AstNodeMath*>(const DfgAtoN* vtxp, AstNodeMath*) {
vtxp->v3fatalSrc("not implemented");
}
template <>
AstCompareNN*
makeNode<AstCompareNN, DfgCompareNN, AstNodeMath*, AstNodeMath*>(const DfgCompareNN* vtxp,
AstNodeMath*, AstNodeMath*) {
vtxp->v3fatalSrc("not implemented");
}
template <>
AstSliceSel* makeNode<AstSliceSel, DfgSliceSel, AstNodeMath*, AstNodeMath*, AstNodeMath*>(
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<const DfgVertex*, AstVar*> m_resultVars;
// Map from an AstVar, to the canonical AstVar that can be substituted for that AstVar
std::unordered_map<AstVar*, AstVar*> 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<const DfgVarPacked*> varps;
vtxp->source(0)->forEachSink([&](const DfgVertex& vtx) {
if (const DfgVarPacked* const varVtxp = vtx.cast<DfgVarPacked>()) {
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<DfgVarPacked>()) {
// This is a DfgVarPacked
varp = getCanonicalVar(thisDfgVarPackedp);
} else if (const DfgVarArray* const thisDfgVarArrayp = vtxp->cast<DfgVarArray>()) {
// This is a DfgVarArray
varp = thisDfgVarArrayp->varp();
} else if (const DfgVarPacked* const sinkDfgVarPackedp = vtxp->findSink<DfgVarPacked>(
[](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<DfgConst>()) return true;
if (vtx.is<DfgVertexVar>()) return true;
if (const DfgArraySel* const selp = vtx.cast<DfgArraySel>()) {
return selp->bitp()->is<DfgConst>();
}
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<AstVar*> 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<DfgVarPacked>()) {
// 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<DfgVarArray>()) {
// 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);
}

305
src/V3DfgOptimizer.cpp Normal file
View File

@ -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 <vector>
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<std::pair<AstNodeMath*, std::vector<const AstVar*>>>;
// Expressions considered for extraction. All the candidates are pure expressions.
AstUser4Allocator<AstNodeModule, Candidates> 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<const AstVar*> 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<DfgGraph> 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<std::unique_ptr<DfgGraph>>& cyclicComponents
= dfg->extractCyclicComponents("cyclic");
// Split the remaining acyclic DFG into [weakly] connected components
const std::vector<std::unique_ptr<DfgGraph>>& 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);
}

View File

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

272
src/V3DfgPasses.cpp Normal file
View File

@ -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 <algorithm>
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<V3Hash, std::vector<DfgVertex*>> 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>() = 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<V3Hash>() = 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<DfgVertex*>& 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<DfgVarPacked>()) {
// 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<DfgVarPacked>()) {
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<DfgVarPacked>()) {
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<DfgVarPacked>();
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<DfgVertex>([&](const DfgVertex& sink) {
if (const DfgVarPacked* const sinkVarp = sink.cast<DfgVarPacked>()) {
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<void()> 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");
}

115
src/V3DfgPasses.h Normal file
View File

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

1594
src/V3DfgPeephole.cpp Normal file

File diff suppressed because it is too large Load Diff

134
src/V3DfgPeephole.h Normal file
View File

@ -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 <V3Stats.h>
#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

286
src/V3DfgVertices.h Normal file
View File

@ -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<DfgVertexVar*>(DfgVertex::verticesNext());
}
DfgVertexVar* verticesPrev() const {
return static_cast<DfgVertexVar*>(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<int>(width), value} {}
ASTGEN_MEMBERS_DfgConst;
DfgConst* verticesNext() const { return static_cast<DfgConst*>(DfgVertex::verticesNext()); }
DfgConst* verticesPrev() const { return static_cast<DfgConst*>(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<size_t>(num().toUQuad());
}
return static_cast<size_t>(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<DfgEdge*, size_t> sourceEdges() override { return {nullptr, 0}; }
std::pair<const DfgEdge*, size_t> 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<FileLine*, uint32_t>;
std::vector<DriverData> 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> driverData{std::move(m_driverData)};
// Grab and unlink the sources
std::vector<DfgVertex*> 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<FileLine*, uint32_t>;
std::vector<DriverData> 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> driverData{std::move(m_driverData)};
// Grab and unlink the sources
std::vector<DfgVertex*> 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

View File

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

View File

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

View File

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

View File

@ -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<AstChangeDet*> 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<VlEvent*> __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<AstExecGraph>(
[&](const AstExecGraph* execGraphp) {
for (const V3GraphVertex* vxp = execGraphp->depGraphp()->verticesBeginp(); vxp;
vxp = vxp->verticesNextp()) {
const ExecMTask* const mtp = static_cast<const ExecMTask*>(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<const ExecMTask*>(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;

View File

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

View File

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

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