Merge from master for release.
This commit is contained in:
commit
db39d70c75
|
|
@ -56,7 +56,6 @@ DisableFormat: false
|
|||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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
84
Changes
|
|
@ -8,29 +8,75 @@ The changes in each Verilator version are described below. The
|
|||
contributors that suggested a given feature are shown in []. Thanks!
|
||||
|
||||
|
||||
Verilator 5.002 2022-10-29
|
||||
==========================
|
||||
|
||||
**Major:**
|
||||
|
||||
* This is a major new release.
|
||||
* Require C++20 for the new --timing features. Upgrading to a C++20 or
|
||||
newer compiler is strongly recommended.
|
||||
* Support the Active and NBA scheduling regions as defined by the
|
||||
SystemVerilog standard (IEEE 1800-2017 chapter 4). This means all generated
|
||||
clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD]
|
||||
* Support timing controls (delays, event controls in any location, wait
|
||||
statements) and forks. [Krzysztof Bieganski, Antmicro Ltd]
|
||||
This may require adding --timing or --no-timing. See docs for details.
|
||||
* Introduce a new combinational logic optimizer (DFG), that can yield
|
||||
significant performance improvements on some designs. [Geza Lore, Shunyao CAD]
|
||||
* Add --binary option as alias of --main --exe --build --timing (#3625).
|
||||
For designs where C++ was only used to make a simple no-I/O testbench, we
|
||||
recommend abandoning that C++, and instead letting Verilator build it
|
||||
with --binary (or --main).
|
||||
|
||||
**Minor:**
|
||||
|
||||
* Split UNUSED warning into genvar, param, and signal warnings (#3607). [Topa Topino]
|
||||
* Support standalone 'this' in classes (#2594) (#3248) (#3675). [Arkadiusz Kozdra, Antmicro Ltd]
|
||||
* Support tristate select/extend (#3604). [Ryszard Rozak, Antmicro Ltd>
|
||||
* Support linting for top module interfaces (#3635). [Kanad Kanhere]
|
||||
* Support virtual interfaces (#3654). [Arkadiusz Kozdra, Antmicro Ltd]
|
||||
* Support class type params without defaults (#3693). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Support empty generate_regions (#3695). [mpb27]
|
||||
* Support access to constructs inside type parameters (#3702). [Arkadiusz Kozdra, Antmicro Ltd]
|
||||
* Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang]
|
||||
* Add --get-supported to determine what features are in Verilator.
|
||||
* Add error on real edge event control.
|
||||
* Fix false LATCH warning on 'unique if' (#3088). [Rachit Nigam]
|
||||
* Fix cell assigning integer array parameters (#3299). [Michael Platzer]
|
||||
* Fix LSB error on --hierarchical submodules (#3539). [danbone]
|
||||
* Fix $display of fixed-width numbers (#3565). [Iztok Jeras]
|
||||
* Fix foreach and pre/post increment in functions (#3613). [Nandu Raj]
|
||||
* Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Fix VPI inline module naming mismatch (#3690) (#3694). [Jiuyang Liu]
|
||||
* Fix deadlock in timeprecision when using systemC (#3707). [Kamil Rakoczy, Antmicro Ltd]
|
||||
* Fix width mismatch on inside operator (#3714). [Alex Torregrosa]
|
||||
|
||||
|
||||
Verilator 4.228 2022-10-01
|
||||
==========================
|
||||
|
||||
**Announcement:**
|
||||
|
||||
* The next release is anticipated primere Verilator Version 5. Please
|
||||
* The next release is anticipated to premiere Verilator Version 5. Please
|
||||
consider beta-testing the github 'develop-v5' branch, which will soon
|
||||
merge into the github 'master' branch (#3383).
|
||||
|
||||
**Minor:**
|
||||
|
||||
* Support some IEEE signal strengths (#3601) (#3629). [Ryszard Rozak/Antmicro]
|
||||
* Support some IEEE signal strengths (#3601) (#3629). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Add --main to generate main() C++ (previously was experimental only).
|
||||
* Add --build-jobs, and rework arguments for -j (#3623). [Kamil Rakoczy]
|
||||
* Rename --bin to --build-dep-bin.
|
||||
* Rename debug flags --dumpi-tree, --dumpi-graph, etc. [Geza Lore]
|
||||
* Fix thread saftey in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic]
|
||||
* Fix thread safety in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic]
|
||||
* Fix crash in gate optimization of circular logic (#3543). [Bill Flynn]
|
||||
* Fix arguments in non-static method call (#3547) (#3582). [Gustav Svensk]
|
||||
* Fix default --mod-prefix when --prefix is repeated (#3603). [Geza Lore]
|
||||
* Fix calling trace() after open() segfault (#3610) (#3627). [Yu-Sheng Lin]
|
||||
* Fix typedef'ed class conversion to boolean (#3616). [Aleksander Kiryk]
|
||||
* Fix Verilation speed when disabled warnings (#3632). [Kamil Rakoczy/Antmicro]
|
||||
* Fix typedef'ed class conversion to Boolean (#3616). [Aleksander Kiryk]
|
||||
* Fix Verilation speed when disabled warnings (#3632). [Kamil Rakoczy, Antmicro Ltd]
|
||||
|
||||
|
||||
Verilator 4.226 2022-08-31
|
||||
|
|
@ -39,31 +85,31 @@ Verilator 4.226 2022-08-31
|
|||
**Minor:**
|
||||
|
||||
* Add --future0 and --future1 options.
|
||||
* Support class parameters (#2231) (#3541). [Arkadiusz Kozdra/Antmicro]
|
||||
* Support wildcard index associative arrays (#3501). [Arkadiusz Kozdra/Antmicro]
|
||||
* Support class parameters (#2231) (#3541). [Arkadiusz Kozdra, Antmicro Ltd]
|
||||
* Support wildcard index associative arrays (#3501). [Arkadiusz Kozdra, Antmicro Ltd]
|
||||
* Support negated properties (#3572). [Aleksander Kiryk]
|
||||
* Support $test$plusargs(expr) (#3489).
|
||||
* Rename trace rolloverSize() (#3570).
|
||||
* Improve Verilation speed with --threads on large designs. [Geza Lore]
|
||||
* Improve Verilation memory by reducing V3Number (#3521). [Mariusz Glebocki/Antmicro]
|
||||
* Improve Verilation memory by reducing V3Number (#3521). [Mariusz Glebocki, Antmicro Ltd]
|
||||
* Fix struct pattern assignment (#2328) (#3517). [Mostafa Gamal]
|
||||
* Fix public combo propagation issues (#2905). [Todd Strader]
|
||||
* Fix incorrect tristate logic (#3399) [shareefj, Vighnesh Iyer]
|
||||
* Fix incorrect bit op tree optimization (#3470). [algrobman]
|
||||
* Fix bisonpre for MSYS2 (#3471).
|
||||
* Fix max memory usage (#3483). [Kamil Rakoczy/Antmicro]
|
||||
* Fix max memory usage (#3483). [Kamil Rakoczy, Antmicro Ltd]
|
||||
* Fix empty string arguments to display (#3484). [Grulfen]
|
||||
* Fix table misoptimizing away display (#3488). [Stefan Post]
|
||||
* Fix table optimizing away display (#3488). [Stefan Post]
|
||||
* Fix unique_ptr memory header for MinGW64 (#3493).
|
||||
* Fix $dump systemtask with --output-split-cfuncs (#3495) (#3497). [Varun Koyyalagunta]
|
||||
* Fix $dump system task with --output-split-cfuncs (#3495) (#3497). [Varun Koyyalagunta]
|
||||
* Fix wrong bit op tree optimization (#3509). [Nathan Graybeal]
|
||||
* Fix nested default assignment for struct pattern (#3511) (#3524). [Mostafa Gamal]
|
||||
* Fix sformat string incorrectly cleared (#3515) (#3519). [Gustav Svensk]
|
||||
* Fix segfault exporting non-existant package (#3535).
|
||||
* Fix segfault exporting non-existent package (#3535).
|
||||
* Fix void-cast queue pop_front or pop_back (#3542) (#3364). [Drew Ranck]
|
||||
* Fix case statement comparing string literal (#3544). [Gustav Svensk]
|
||||
* Fix === with some tristate constants (#3551). [Ryszard Rozak/Antmicro]
|
||||
* Fix converting subclasses to string (#3552). [Arkadiusz Kozdra/Antmicro]
|
||||
* Fix === with some tristate constants (#3551). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Fix converting classes to string (#3552). [Arkadiusz Kozdra, Antmicro Ltd]
|
||||
* Fix --hierarchical with order-based pin connections (#3583) (#3585). [Kelin9298]
|
||||
|
||||
|
||||
|
|
@ -85,7 +131,7 @@ Verilator 4.224 2022-06-19
|
|||
* Improve conditional merging optimization (#3125). [Geza Lore, Shunyao CAD]
|
||||
* Define VM_TRACE_VCD when tracing in VCD format. [Geza Lore, Shunyao CAD]
|
||||
* Add assert when VerilatedContext is mis-deleted (#3121). [Rupert Swarbrick]
|
||||
* Internal prep work towards timing control. [Krzysztof Bieganski/Antmicro]
|
||||
* Internal prep work towards timing control. [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Fix hang with large case statement optimization (#3405). [Mike Urbach]
|
||||
* Fix UNOPTFLAT warning from initial static var (#3406). [Kamil Rakoczy]
|
||||
* Fix compile error when enable VL_LEAK_CHECKS (#3411). [HungMingWu]
|
||||
|
|
@ -252,7 +298,7 @@ Verilator 4.212 2021-09-01
|
|||
* Fix re-evaluation of logic dependent on state set in DPI exports (#3091). [Geza Lore]
|
||||
* Support unpacked array localparams in tasks/functions (#3078). [Geza Lore]
|
||||
* Support timeunit/timeprecision in $unit.
|
||||
* Support assignment patterns as children of pins (#3041). [Krzysztof Bieganski/Antmicro]
|
||||
* Support assignment patterns as children of pins (#3041). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Add --instr-count-dpi to tune assumed DPI import cost for multithreaded
|
||||
model scheduling. Default value changed to 200 (#3068). [Yinan Xu]
|
||||
* Output files are split based on the set of headers required
|
||||
|
|
@ -317,7 +363,7 @@ Verilator 4.204 2021-06-12
|
|||
* Prep work towards better ccache hashing/performance. [Geza Lore]
|
||||
* Fix assertion failure in bitOpTree optimization (#2891) (#2899). [Raynard Qiao]
|
||||
* Fix DPI functions not seen as vpiModule (#2893). [Todd Strader]
|
||||
* Fix bounds check in VL_SEL_IWII (#2910). [Krzysztof Bieganski/Antmicro]
|
||||
* Fix bounds check in VL_SEL_IWII (#2910). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Fix slowdown in elaboration (#2911). [Nathan Graybeal]
|
||||
* Fix initialization of assoc in assoc array (#2914). [myftptoyman]
|
||||
* Fix make support for gmake 3.x (#2920) (#2921). [Philipp Wagner]
|
||||
|
|
@ -432,7 +478,7 @@ Verilator 4.108 2021-01-10
|
|||
**Major:**
|
||||
|
||||
* Many VPI changes for IEEE compatibility, which may alter behavior from previous releases.
|
||||
* Support randomize() class method and rand (#2607). [Krzysztof Bieganski/Antmicro]
|
||||
* Support randomize() class method and rand (#2607). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
|
||||
**Minor:**
|
||||
|
||||
|
|
@ -492,7 +538,7 @@ Verilator 4.104 2020-11-14
|
|||
* Support queue and associative array 'with' statements (#2616).
|
||||
* Support queue slicing (#2326).
|
||||
* Support associative array pattern assignments and defaults.
|
||||
* Support static methods and typedefs in classes (#2615). [Krzysztof Bieganski/Antmicro]
|
||||
* Support static methods and typedefs in classes (#2615). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Add error on typedef referencing self (#2539). [Cody Piersall]
|
||||
* With --debug, turn off address space layout randomization.
|
||||
* Fix iteration over mutating list bug in VPI (#2588). [Kaleb Barrett]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
32
configure.ac
32
configure.ac
|
|
@ -10,7 +10,7 @@
|
|||
# Then 'make maintainer-dist'
|
||||
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
|
||||
#AC_INIT([Verilator],[#.### devel])
|
||||
AC_INIT([Verilator],[4.228 2022-10-01],
|
||||
AC_INIT([Verilator],[5.002 2022-10-29],
|
||||
[https://verilator.org],
|
||||
[verilator],[https://verilator.org])
|
||||
|
||||
|
|
@ -374,6 +374,36 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wlogical-op)
|
|||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wthread-safety)
|
||||
AC_SUBST(CFG_CXXFLAGS_WEXTRA)
|
||||
|
||||
# Flags for coroutine support for dynamic scheduling
|
||||
_MY_CXX_CHECK_IFELSE(
|
||||
-fcoroutines-ts,
|
||||
[CFG_CXXFLAGS_COROUTINES="-fcoroutines-ts"],
|
||||
[CFG_CXXFLAGS_COROUTINES="-fcoroutines"])
|
||||
AC_SUBST(CFG_CXXFLAGS_COROUTINES)
|
||||
|
||||
# HAVE_COROUTINES
|
||||
# Check if coroutines are supported at all
|
||||
AC_MSG_CHECKING([whether coroutines are supported by $CXX])
|
||||
ACO_SAVE_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $CFG_CXXFLAGS_COROUTINES"
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM([
|
||||
#ifdef __clang__
|
||||
#define __cpp_impl_coroutine 1
|
||||
#endif
|
||||
#include <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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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> ]]]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
*.dmp
|
||||
*.log
|
||||
*.csrc
|
||||
*.vcd
|
||||
obj_*
|
||||
logs
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
######################################################################
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
//=========================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
382
src/V3Active.cpp
382
src/V3Active.cpp
|
|
@ -129,7 +129,7 @@ public:
|
|||
// Start a new if/else tracking graph
|
||||
// See NODE STATE comment in ActiveLatchCheckVisitor
|
||||
AstNode::user1ClearTree();
|
||||
m_curVertexp = new LatchDetectGraphVertex(this, "ROOT");
|
||||
m_curVertexp = new LatchDetectGraphVertex{this, "ROOT"};
|
||||
}
|
||||
// Clear out userp field of referenced outputs on destruction
|
||||
// (occurs at the end of each combinational always block)
|
||||
|
|
@ -165,7 +165,7 @@ public:
|
|||
} else {
|
||||
outVertexp = castVertexp(nodep->varp()->user1p());
|
||||
}
|
||||
new V3GraphEdge(this, m_curVertexp, outVertexp, 1);
|
||||
new V3GraphEdge{this, m_curVertexp, outVertexp, 1};
|
||||
}
|
||||
// Run latchCheckInternal on each variable assigned by the always block to see if all control
|
||||
// paths make an assignment. Detected latches are flagged in the variables AstVar
|
||||
|
|
@ -202,8 +202,10 @@ class ActiveNamer final : public VNVisitor {
|
|||
private:
|
||||
// STATE
|
||||
AstScope* m_scopep = nullptr; // Current scope to add statement to
|
||||
AstActive* m_iActivep = nullptr; // For current scope, the IActive we're building
|
||||
AstActive* m_cActivep = nullptr; // For current scope, the SActive(combo) we're building
|
||||
AstActive* m_sActivep = nullptr; // For current scope, the Static active we're building
|
||||
AstActive* m_iActivep = nullptr; // For current scope, the Initial active we're building
|
||||
AstActive* m_fActivep = nullptr; // For current scope, the Final active we're building
|
||||
AstActive* m_cActivep = nullptr; // For current scope, the Combo active we're building
|
||||
|
||||
// Map from AstSenTree (equivalence) to the corresponding AstActive created.
|
||||
std::unordered_map<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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
121
src/V3Assert.cpp
121
src/V3Assert.cpp
|
|
@ -47,6 +47,7 @@ private:
|
|||
VDouble0 m_statAsNotImm; // Statistic tracking
|
||||
VDouble0 m_statAsImm; // Statistic tracking
|
||||
VDouble0 m_statAsFull; // Statistic tracking
|
||||
bool m_inSampled = false; // True inside a sampled expression
|
||||
|
||||
// METHODS
|
||||
string assertDisplayMessage(AstNode* nodep, const string& prefix, const string& message) {
|
||||
|
|
@ -58,7 +59,7 @@ private:
|
|||
nodep->displayType(VDisplayType::DT_WRITE);
|
||||
nodep->fmtp()->text(assertDisplayMessage(nodep, prefix, nodep->fmtp()->text()));
|
||||
// cppcheck-suppress nullPointer
|
||||
AstNode* const timenewp = new AstTime(nodep->fileline(), m_modp->timeunit());
|
||||
AstNode* const timenewp = new AstTime{nodep->fileline(), m_modp->timeunit()};
|
||||
if (AstNode* const timesp = nodep->fmtp()->exprsp()) {
|
||||
timesp->unlinkFrBackWithNext();
|
||||
timenewp->addNext(timesp);
|
||||
|
|
@ -68,13 +69,18 @@ private:
|
|||
nodep->fmtp()->scopeNamep(new AstScopeName{nodep->fileline(), true});
|
||||
}
|
||||
}
|
||||
AstSampled* newSampledExpr(AstNode* nodep) {
|
||||
const auto sampledp = new AstSampled{nodep->fileline(), nodep};
|
||||
sampledp->dtypeFrom(nodep);
|
||||
return sampledp;
|
||||
}
|
||||
AstVarRef* newMonitorNumVarRefp(AstNode* nodep, VAccess access) {
|
||||
if (!m_monitorNumVarp) {
|
||||
m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum",
|
||||
nodep->findUInt64DType()};
|
||||
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorNumVarp);
|
||||
}
|
||||
const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorNumVarp, access);
|
||||
const auto varrefp = new AstVarRef{nodep->fileline(), m_monitorNumVarp, access};
|
||||
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
|
||||
return varrefp;
|
||||
}
|
||||
|
|
@ -84,7 +90,7 @@ private:
|
|||
nodep->findBitDType()};
|
||||
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorOffVarp);
|
||||
}
|
||||
const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorOffVarp, access);
|
||||
const auto varrefp = new AstVarRef{nodep->fileline(), m_monitorOffVarp, access};
|
||||
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
|
||||
return varrefp;
|
||||
}
|
||||
|
|
@ -92,16 +98,17 @@ private:
|
|||
// Add a internal if to check assertions are on.
|
||||
// Don't make this a AND term, as it's unlikely to need to test this.
|
||||
FileLine* const fl = nodep->fileline();
|
||||
AstNode* const newp = new AstIf(
|
||||
AstNodeIf* const newp = new AstIf{
|
||||
fl,
|
||||
(force ? new AstConst(fl, AstConst::BitTrue())
|
||||
(force ? new AstConst{fl, AstConst::BitTrue{}}
|
||||
: // If assertions are off, have constant propagation rip them out later
|
||||
// This allows syntax errors and such to be detected normally.
|
||||
(v3Global.opt.assertOn()
|
||||
? static_cast<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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
733
src/V3Ast.h
733
src/V3Ast.h
File diff suppressed because it is too large
Load Diff
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
389
src/V3Clock.cpp
389
src/V3Clock.cpp
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue