Merge from master for release.
This commit is contained in:
commit
e29f5ad559
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-18.04]
|
||||
os: [ubuntu-22.04, ubuntu-20.04, ubuntu-18.04]
|
||||
compiler:
|
||||
- { cc: clang, cxx: clang++ }
|
||||
- { cc: gcc, cxx: g++ }
|
||||
|
|
@ -37,9 +37,16 @@ jobs:
|
|||
exclude:
|
||||
# Build pull requests only with ubuntu-20.04 and without m32
|
||||
- os: ${{ github.event_name == 'pull_request' && 'ubuntu-18.04' || 'do-not-exclude' }}
|
||||
- os: ${{ github.event_name == 'pull_request' && 'ubuntu-22.04' || 'do-not-exclude' }}
|
||||
- m32: ${{ github.event_name == 'pull_request' && 1 || 'do-not-exclude' }}
|
||||
# Build -m32 only on ubuntu-20.04
|
||||
- {os: ubuntu-18.04, m32: 1}
|
||||
- {os: ubuntu-22.04, m32: 1}
|
||||
include:
|
||||
# Build GCC 10 on ubuntu-20.04
|
||||
- os: ubuntu-20.04
|
||||
compiler: { cc: gcc-10, cxx: g++-10 }
|
||||
m32: 0
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Build | ${{ matrix.os }} | ${{ matrix.compiler.cc }} ${{ matrix.m32 && '| -m32' || '' }}
|
||||
env:
|
||||
|
|
@ -90,7 +97,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-18.04]
|
||||
os: [ubuntu-22.04, ubuntu-20.04, ubuntu-18.04]
|
||||
compiler:
|
||||
- { cc: clang, cxx: clang++ }
|
||||
- { cc: gcc, cxx: g++ }
|
||||
|
|
@ -99,9 +106,18 @@ jobs:
|
|||
exclude:
|
||||
# Build pull requests only with ubuntu-20.04 and without m32
|
||||
- os: ${{ github.event_name == 'pull_request' && 'ubuntu-18.04' || 'do-not-exclude' }}
|
||||
- os: ${{ github.event_name == 'pull_request' && 'ubuntu-22.04' || 'do-not-exclude' }}
|
||||
- m32: ${{ github.event_name == 'pull_request' && 1 || 'do-not-exclude' }}
|
||||
# Build -m32 only on ubuntu-20.04
|
||||
- {os: ubuntu-18.04, m32: 1}
|
||||
- {os: ubuntu-22.04, m32: 1}
|
||||
include:
|
||||
# Test with GCC 10 on ubuntu-20.04 without m32
|
||||
- {os: ubuntu-20.04, compiler: { cc: gcc-10, cxx: g++-10 }, m32: 0, suite: dist-vlt-0}
|
||||
- {os: ubuntu-20.04, compiler: { cc: gcc-10, cxx: g++-10 }, m32: 0, suite: dist-vlt-1}
|
||||
- {os: ubuntu-20.04, compiler: { cc: gcc-10, cxx: g++-10 }, m32: 0, suite: dist-vlt-2}
|
||||
- {os: ubuntu-20.04, compiler: { cc: gcc-10, cxx: g++-10 }, m32: 0, suite: vltmt-0}
|
||||
- {os: ubuntu-20.04, compiler: { cc: gcc-10, cxx: g++-10 }, m32: 0, suite: vltmt-1}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Test | ${{ matrix.os }} | ${{ matrix.compiler.cc }} | ${{ matrix.suite }} ${{ matrix.m32 && '| -m32' || '' }}
|
||||
env:
|
||||
|
|
@ -110,7 +126,7 @@ jobs:
|
|||
CI_M32: ${{ matrix.m32 }}
|
||||
CC: ${{ matrix.compiler.cc }}
|
||||
CXX: ${{ matrix.compiler.cxx }}
|
||||
CACHE_BASE_KEY: test-${{ matrix.os }}-${{ matrix.compiler.cc }}-m32=${{ matrix.m32 }}-${ matrix.suite }}
|
||||
CACHE_BASE_KEY: test-${{ matrix.os }}-${{ matrix.compiler.cc }}-m32=${{ matrix.m32 }}-${{ matrix.suite }}
|
||||
CCACHE_MAXSIZE: 64M # Per build matrix entry (2160M in total)
|
||||
VERILATOR_ARCHIVE: verilator-${{ github.sha }}-${{ matrix.os }}-${{ matrix.compiler.cc }}${{ matrix.m32 && '-m32' || '' }}.tar.gz
|
||||
steps:
|
||||
|
|
|
|||
49
Changes
49
Changes
|
|
@ -8,6 +8,37 @@ The changes in each Verilator version are described below. The
|
|||
contributors that suggested a given feature are shown in []. Thanks!
|
||||
|
||||
|
||||
Verilator 4.223 devel
|
||||
==========================
|
||||
|
||||
**Major:**
|
||||
|
||||
* VCD tracing is now parallelized with --threads (#3449). [Geza Lore, Shunyao CAD]
|
||||
|
||||
**Minor:**
|
||||
|
||||
* Add -f<optimization> options to replace -O<letter> options (#3436).
|
||||
* Changed --no-merge-const-pool to -fno-merge-const-pool (#3436).
|
||||
* Changed --no-decoration to remove output whitespace (#3460). [Kamil Rakoczy]
|
||||
* Support compile time trace signal selection with tracing_on/off (#3323). [Shunyao CAD]
|
||||
* Support non-ANSI interface port declarations (#3439). [Geza Lore, Shunyao CAD]
|
||||
* Support concat assignment to packed array (#3446).
|
||||
* 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]
|
||||
* 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]
|
||||
* Fix cmake rules to support higher-level targets (#3377) (#3386). [Martin Stadler]
|
||||
* Fix BLKANDNBLK on $readmem/$writemem (#3379). [Alex Solomatnikov]
|
||||
* Fix 'with' operator with type casting (#3387). [xiak95]
|
||||
* Fix incorrect conditional merging (#3409). [Raynard Qiao]
|
||||
* Fix passing VL_TRACE_FST_WRITER_THREAD in CMake build. [Geza Lore, Shunyao CAD]
|
||||
* Fix compile error under strict C++11 mode (#3463). [Kevin Kiningham]
|
||||
* Fix public unpacked input ports (#3465). [Todd Strader]
|
||||
|
||||
|
||||
Verilator 4.222 2022-05-02
|
||||
==========================
|
||||
|
||||
|
|
@ -43,7 +74,7 @@ Verilator 4.220 2022-03-12
|
|||
* Add VERILATOR_VERSION_INTEGER for determining API (#3343). [Larry Doolittle]
|
||||
* Improve various V3Combine algorithm details (#3328). [Yutetsu TAKATSUKASA]
|
||||
* Improve various V3Order algorithm details. [Geza Lore]
|
||||
* Fix macOS arm64 build (#3285) (#3291). [Guokai Chen]
|
||||
* Fix MacOS arm64 build (#3285) (#3291). [Guokai Chen]
|
||||
* Fix signed number operation (#3294) (#3308). [Raynard Qiao]
|
||||
* Fix FST traces to include vector range (#3296) (#3297). [Jamie Iles]
|
||||
* Fix skipping public enum values with four-state values (#3303).
|
||||
|
|
@ -108,7 +139,7 @@ Verilator 4.216 2021-12-05
|
|||
**Minor:**
|
||||
|
||||
* Internal code cleanups and improvements. [Geza Lore]
|
||||
* Improve --thread verilation-time performance.
|
||||
* Improve --thread Verilation-time performance.
|
||||
* Support task name in $display %m (#3211). [Julie Schwartz]
|
||||
* Make 'bit', 'logic' and 'time' types unsigned by default. [Geza Lore]
|
||||
* Optimize $random concatenates/selects (#3114).
|
||||
|
|
@ -132,7 +163,7 @@ Verilator 4.214 2021-10-17
|
|||
|
||||
**Major:**
|
||||
|
||||
* Add profile-guided optmization of mtasks (#3150).
|
||||
* Add profile-guided optimization of mtasks (#3150).
|
||||
|
||||
**Minor:**
|
||||
|
||||
|
|
@ -198,7 +229,7 @@ Verilator 4.210 2021-07-07
|
|||
|
||||
* Add --prof-c to pass profiling to compiler (#3059). [Alexander Grobman]
|
||||
* Optimize a lot more model variables into function locals (#3027). [Geza Lore]
|
||||
* Support middle-of-design nested topmodules (#3026). [Dan Petrisko]
|
||||
* Support middle-of-design nested top modules (#3026). [Dan Petrisko]
|
||||
* Remove deprecated --no-relative-cfuncs option (#3024). [Geza Lore]
|
||||
* Remove deprecated --inhibit-sim option (#3035). [Geza Lore]
|
||||
* Merge const static data globally into a new constant pool (#3013). [Geza Lore]
|
||||
|
|
@ -237,10 +268,10 @@ Verilator 4.204 2021-06-12
|
|||
* Fix to emit 'else if' without nesting (#2944). [Geza Lore]
|
||||
* Fix part select issues in LATCH warning (#2948) (#2938). [Julien Margetts]
|
||||
* Fix to not emit empty files with low split limits (#2961). [Geza Lore]
|
||||
* Fix merging of assignments in C++ code (#2970). [Ruper Swarbrick]
|
||||
* Fix merging of assignments in C++ code (#2970). [Rupert Swarbrick]
|
||||
* Fix unused variable warnings (#2991). [Pieter Kapsenberg]
|
||||
* Fix --protect-ids when using SV classes (#2994). [Geza Lore]
|
||||
* Fix constant function calls with uninit value (#2995). [yanx21]
|
||||
* Fix constant function calls with uninitialized value (#2995). [yanx21]
|
||||
* Fix Makefiles to support Windows EXEEXT usage (#3008). [Miodrag Milanovic]
|
||||
|
||||
|
||||
|
|
@ -278,7 +309,7 @@ Verilator 4.202 2021-04-24
|
|||
* Fix Cygwin example compile issues (#2856). [Mark Shaw]
|
||||
* Fix select of with index variable (#2880). [Alexander Grobman]
|
||||
* Fix cmake version number to be numeric (#2881). [Yuri Victorovich]
|
||||
* Fix MinGW not supportting 'localtime_r' (#2882). [HyungKi Jeong]
|
||||
* Fix MinGW not supporting 'localtime_r' (#2882). [HyungKi Jeong]
|
||||
* Fix cast from packed, typedef'ed interface signal (#2884). [Todd Strader]
|
||||
* Fix VPI package reported as vpiModule (#2885). [Todd Strader]
|
||||
* Fix dumping waveforms to multiple FST files (#2889). [David Metz]
|
||||
|
|
@ -425,7 +456,7 @@ Verilator 4.102 2020-10-15
|
|||
* Support # as a comment in -f files (#2497). [phantom-killua]
|
||||
* Support 'this' (#2585). [Rafal Kapuscik]
|
||||
* Support defines for FST tracing (#2592). [Markus Krause]
|
||||
* Support non-overapping implication inside properties (#1292). [Peter Monsson]
|
||||
* Support non-overlapping implication inside properties (#1292). [Peter Monsson]
|
||||
* Fix timescale with --hierarchical (#2554). [Yutetsu TAKATSUKASA]
|
||||
* Fix cmake build with --hierarchical (#2560). [Yutetsu TAKATSUKASA]
|
||||
* Fix -G dropping public indication (#2561). [Andrew Goessling]
|
||||
|
|
@ -451,7 +482,7 @@ Verilator 4.100 2020-09-07
|
|||
* Support (with limitations) class extern, class extends, virtual class.
|
||||
* Support $urandom, $urandom_range without stability.
|
||||
* Support assume property. [Peter Monsson]
|
||||
* Support non-overapping implication inside properties (#1292). [Peter Monsson]
|
||||
* Support non-overlapping implication inside properties (#1292). [Peter Monsson]
|
||||
* Fix false DECLFILENAME on black-boxed modules (#2430). [Philipp Wagner]
|
||||
* Fix naming of "id : begin" blocks.
|
||||
* Fix class constructor error on assignments to const.
|
||||
|
|
|
|||
21
README.rst
21
README.rst
|
|
@ -21,7 +21,7 @@ Welcome to Verilator
|
|||
.. list-table::
|
||||
|
||||
* - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.**
|
||||
* Accepts synthesizable Verilog or SystemVerilog
|
||||
* Accepts Verilog or SystemVerilog
|
||||
* Performs lint code-quality checks
|
||||
* Compiles into multithreaded C++, or SystemC
|
||||
* Creates XML to front-end your own tools
|
||||
|
|
@ -57,17 +57,18 @@ files, the "Verilated" code.
|
|||
|
||||
The user writes a little C++/SystemC wrapper file, which instantiates the
|
||||
"Verilated" model of the user's top level module. These C++/SystemC files
|
||||
are then compiled by a C++ compiler (gcc/clang/MSVC++). The resulting
|
||||
executable performs the design simulation. Verilator also supports linking
|
||||
its generated libraries, optionally encrypted, into other simulators.
|
||||
are then compiled by a C++ compiler (gcc/clang/MSVC++). Executing the
|
||||
resulting executable performs the design simulation. Verilator also
|
||||
supports linking Verilated generated libraries, optionally encrypted, into
|
||||
other simulators.
|
||||
|
||||
Verilator may not be the best choice if you are expecting a full featured
|
||||
replacement for NC-Verilog, VCS or another commercial Verilog simulator, or
|
||||
if you are looking for a behavioral Verilog simulator e.g. for a quick
|
||||
class project (we recommend `Icarus Verilog`_ for this.) However, if you
|
||||
are looking for a path to migrate SystemVerilog to C++ or SystemC, or your
|
||||
team is comfortable writing just a touch of C++ code, Verilator is the tool
|
||||
for you.
|
||||
replacement for Incisive, ModelSim/Questa, VCS or another commercial
|
||||
Verilog simulator, or if you are looking for a behavioral Verilog simulator
|
||||
e.g. for a quick class project (we recommend `Icarus Verilog`_ for this.)
|
||||
However, if you are looking for a path to migrate SystemVerilog to C++ or
|
||||
SystemC, or your team is comfortable writing just a touch of C++ code,
|
||||
Verilator is the tool for you.
|
||||
|
||||
|
||||
Performance
|
||||
|
|
|
|||
|
|
@ -319,6 +319,7 @@ detailed descriptions of these arguments.
|
|||
-f <file> Parse arguments from a file
|
||||
-FI <file> Force include of a file
|
||||
--flatten Force inlining of all modules, tasks and functions
|
||||
-fno-<optimization> Disable internal optimization stage
|
||||
-G<name>=<value> Overwrite top-level parameter
|
||||
--gdb Run Verilator under GDB interactively
|
||||
--gdbbt Run Verilator under GDB for backtrace
|
||||
|
|
@ -344,7 +345,6 @@ detailed descriptions of these arguments.
|
|||
--MMD Create .d dependency files
|
||||
--MP Create phony dependency targets
|
||||
--Mdir <directory> Name of output object directory
|
||||
--no-merge-const-pool Disable merging of different types in const pool
|
||||
--mod-prefix <topname> Name to prepend to lower classes
|
||||
--no-clk <signal-name> Prevent marking specified signal as clock
|
||||
--no-decoration Disable comments and symbol decorations
|
||||
|
|
@ -405,7 +405,7 @@ detailed descriptions of these arguments.
|
|||
--trace-max-width <width> Maximum array depth for tracing
|
||||
--trace-params Enable tracing of parameters
|
||||
--trace-structs Enable tracing structure names
|
||||
--trace-threads <threads> Enable waveform creation on separate threads
|
||||
--trace-threads <threads> Enable FST waveform creation on separate threads
|
||||
--trace-underscore Enable tracing of _signals
|
||||
-U<var> Undefine preprocessor define
|
||||
--unroll-count <loops> Tune maximum loop iterations
|
||||
|
|
|
|||
|
|
@ -54,8 +54,12 @@ if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
|
|||
|
||||
if [ "$CI_OS_NAME" = "linux" ]; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install libfl-dev libgoogle-perftools-dev ccache
|
||||
if [ "$CI_RUNS_ON" = "ubuntu-20.04" ]; then
|
||||
sudo apt-get install libfl-dev ccache
|
||||
if [ "$CI_RUNS_ON" != "ubuntu-22.04" ]; then
|
||||
# Some conflict of libunwind verison on 22.04, can live without it for now
|
||||
sudo apt-get install libgoogle-perftools-dev
|
||||
fi
|
||||
if [ "$CI_RUNS_ON" = "ubuntu-20.04" ] || [ "$CI_RUNS_ON" = "ubuntu-22.04" ]; then
|
||||
sudo apt-get install libsystemc libsystemc-dev
|
||||
fi
|
||||
if [ "$COVERAGE" = 1 ]; then
|
||||
|
|
@ -85,7 +89,7 @@ elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
|
|||
sudo apt-get update
|
||||
# libfl-dev needed for internal coverage's test runs
|
||||
sudo apt-get install gdb gtkwave lcov libfl-dev ccache
|
||||
if [ "$CI_RUNS_ON" = "ubuntu-20.04" ]; then
|
||||
if [ "$CI_RUNS_ON" = "ubuntu-20.04" ] || [ "$CI_RUNS_ON" = "ubuntu-22.04" ]; then
|
||||
sudo apt-get install libsystemc-dev
|
||||
fi
|
||||
if [ "$CI_M32" = 1 ]; then
|
||||
|
|
|
|||
14
configure.ac
14
configure.ac
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
|
||||
#AC_INIT([Verilator],[#.### devel])
|
||||
AC_INIT([Verilator],[4.222 2022-05-02],
|
||||
AC_INIT([Verilator],[4.224 2022-06-19],
|
||||
[https://verilator.org],
|
||||
[verilator],[https://verilator.org])
|
||||
# When releasing, also update header of Changes file
|
||||
|
|
@ -348,14 +348,18 @@ AC_SUBST(CFG_CXXFLAGS_PROFILE)
|
|||
|
||||
# Flag to select newest language standard supported
|
||||
# Macros work such that first option that passes is the one we take
|
||||
# Currently enabled c++14 due to packaged SystemC dependency
|
||||
# c++14 is the newest that Verilator is regressed to support
|
||||
# Currently enable c++17/c++14 due to packaged SystemC dependency
|
||||
# c++17 is the newest that Verilator is regularly tested to support
|
||||
# c++11 is the oldest that Verilator supports
|
||||
# gnu is requried for Cygwin to compile verilated.h successfully
|
||||
#_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++20)
|
||||
#_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=c++20)
|
||||
#_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++17)
|
||||
#_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=c++17)
|
||||
case "$(which lsb_release 2>&1 > /dev/null && lsb_release -d)" in
|
||||
*Ubuntu*22.04*)
|
||||
_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++17)
|
||||
_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=c++17)
|
||||
;;
|
||||
esac
|
||||
_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++14)
|
||||
_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=c++14)
|
||||
_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++11)
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
*.html
|
||||
*.pdf
|
||||
_build
|
||||
guide/spelling.txt
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ Guokai Chen
|
|||
Harald Heckmann
|
||||
Howard Su
|
||||
Huang Rui
|
||||
Huanghuang Zhou
|
||||
HungMingWu
|
||||
HyungKi Jeong
|
||||
Iru Cai
|
||||
|
|
@ -71,6 +72,7 @@ Markus Krause
|
|||
Marlon James
|
||||
Marshal Qiao
|
||||
Martin Schmidt
|
||||
Martin Stadler
|
||||
Matthew Ballance
|
||||
Michael Killough
|
||||
Michaël Lefebvre
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ default:
|
|||
# Intermediate rules
|
||||
|
||||
vl-extract: ../bin/verilator ../Changes
|
||||
ln -sf ../spelling.txt guide/spelling.txt
|
||||
mkdir -p _build/gen
|
||||
$(PYTHON3) bin/vl_sphinx_extract ../bin/verilator
|
||||
sed 's/`/\&96;/g' < ../Changes > _build/gen/Changes
|
||||
|
|
@ -77,6 +78,7 @@ clean mostlyclean distclean maintainer-clean::
|
|||
rm -f *.pg *.pgs *.toc *.tp *.tps *.vr *.vrs *.idx
|
||||
rm -f *.ev *.evs *.ov *.ovs *.cv *.cvs *.ma *.mas
|
||||
rm -f *.tex
|
||||
rm -f guide/spelling.txt
|
||||
|
||||
distclean maintainer-clean::
|
||||
rm -f *.info* *.1 *.html *.pdf $(INFOS)
|
||||
|
|
|
|||
|
|
@ -110,6 +110,9 @@ model. Here is a simple example:
|
|||
Verilated::commandArgs(argc, argv); // Remember args
|
||||
|
||||
top = new Vtop; // Create model
|
||||
// Do not instead make Vtop as a file-scope static
|
||||
// variable, as the "C++ static initialization order fiasco"
|
||||
// may cause a crash
|
||||
|
||||
top->reset_l = 0; // Set some inputs
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ Option `--cdc`
|
|||
The experimental `--cdc` option is believed to be generally unused and is
|
||||
planned for removal no sooner than January 2023.
|
||||
|
||||
Option `-O<letter>`
|
||||
The debug `-O<letter>` options have been replaced with
|
||||
`-fno-<optimization>` debug options to match GCC. The old options are
|
||||
planned for removal no sooner than June 2023.
|
||||
|
||||
Option `--prof-threads`
|
||||
The `--prof-threads` option has been superseded by the `--prof-exec` and
|
||||
`--prof-pgo` options and is planned for removal no sooner than April 2023.
|
||||
|
|
|
|||
|
|
@ -431,6 +431,54 @@ Summary:
|
|||
flattening large designs may require significant CPU time, memory and
|
||||
storage.
|
||||
|
||||
.. option:: -fno-acyc-simp
|
||||
|
||||
.. option:: -fno-assemble
|
||||
|
||||
.. option:: -fno-case
|
||||
|
||||
.. option:: -fno-combine
|
||||
|
||||
.. option:: -fno-const
|
||||
|
||||
.. option:: -fno-const-bit-op-tree
|
||||
|
||||
.. option:: -fno-dedup
|
||||
|
||||
.. option:: -fno-expand
|
||||
|
||||
.. option:: -fno-gate
|
||||
|
||||
.. option:: -fno-inline
|
||||
|
||||
.. option:: -fno-life
|
||||
|
||||
.. option:: -fno-life-post
|
||||
|
||||
.. option:: -fno-localize
|
||||
|
||||
.. option:: -fno-merge-cond
|
||||
|
||||
.. option:: -fno-merge-cond-motion
|
||||
|
||||
.. option:: -fno-merge-const-pool
|
||||
|
||||
.. option:: -fno-reloop
|
||||
|
||||
.. option:: -fno-reorder
|
||||
|
||||
.. option:: -fno-split
|
||||
|
||||
.. option:: -fno-subst
|
||||
|
||||
.. option:: -fno-subst-const
|
||||
|
||||
.. option:: -fno-table
|
||||
|
||||
Rarely needed. Disables one of the internal optimization steps. These
|
||||
are typically used only when recommended by a maintainer to help debug
|
||||
or work around an issue.
|
||||
|
||||
.. option:: -G<name>=<value>
|
||||
|
||||
Overwrites the given parameter of the toplevel module. The value is
|
||||
|
|
@ -648,13 +696,6 @@ Summary:
|
|||
The directory is created if it does not exist and the parent directories
|
||||
exist; otherwise manually create the Mdir before calling Verilator.
|
||||
|
||||
.. option:: --no-merge-const-pool
|
||||
|
||||
Rarely needed. In order to minimize cache footprint, values of different
|
||||
data type, that are yet emitted identically in C++ are merged in the
|
||||
constant pool. This option disables this and causes every constant pool
|
||||
entry with a distinct data type to be emitted separately.
|
||||
|
||||
.. option:: --mod-prefix <topname>
|
||||
|
||||
Specifies the name to prepend to all lower level classes. Defaults to
|
||||
|
|
@ -703,9 +744,9 @@ Summary:
|
|||
|
||||
Rarely needed. Enables or disables a specific optimizations, with the
|
||||
optimization selected based on the letter passed. A lowercase letter
|
||||
disables an optimization, an upper case letter enables it. This is
|
||||
intended for debugging use only; see the source code for
|
||||
version-dependent mappings of optimizations to -O letters.
|
||||
disables an optimization, an upper case letter enables it. This option
|
||||
is deprecated and the various `-f<optimization>` arguments should be
|
||||
used instead.
|
||||
|
||||
.. option:: -o <executable>
|
||||
|
||||
|
|
@ -847,12 +888,12 @@ Summary:
|
|||
|
||||
.. option:: --prof-exec
|
||||
|
||||
Enable collection of execution trace, that can be convered into a gantt
|
||||
Enable collection of execution trace, that can be converted into a gantt
|
||||
chart with verilator_gantt See :ref:`Execution Profiling`.
|
||||
|
||||
.. option:: --prof-pgo
|
||||
|
||||
Enable collection of profiling data for profile guided verilation. Currently
|
||||
Enable collection of profiling data for profile guided Verilation. Currently
|
||||
this is only useful with :vlopt:`--threads`. See :ref:`Thread PGO`.
|
||||
|
||||
.. option:: --prof-threads
|
||||
|
|
@ -1041,7 +1082,8 @@ Summary:
|
|||
is not thread safe. With "--threads 1", the generated model is single
|
||||
threaded but may run in a multithreaded environment. With "--threads N",
|
||||
where N >= 2, the model is generated to run multithreaded on up to N
|
||||
threads. See :ref:`Multithreading`.
|
||||
threads. See :ref:`Multithreading`. This option also applies to
|
||||
:vlopt:`--trace` (but not :vlopt:`--trace-fst`).
|
||||
|
||||
.. option:: --threads-dpi all
|
||||
|
||||
|
|
@ -1119,7 +1161,8 @@ Summary:
|
|||
Having tracing compiled in may result in some small performance losses,
|
||||
even when tracing is not turned on during model execution.
|
||||
|
||||
See also :vlopt:`--trace-threads` option.
|
||||
When using :vlopt:`--threads`, VCD tracing is parallelized, using the
|
||||
same number of threads as passed to :vlopt:`--threads`.
|
||||
|
||||
.. option:: --trace-coverage
|
||||
|
||||
|
|
@ -1173,12 +1216,12 @@ Summary:
|
|||
.. option:: --trace-threads *threads*
|
||||
|
||||
Enable waveform tracing using separate threads. This is typically faster
|
||||
in simulation runtime but uses more total compute. This option is
|
||||
independent of, and works with, both :vlopt:`--trace` and
|
||||
:vlopt:`--trace-fst`. Different trace formats can take advantage of
|
||||
more trace threads to varying degrees. Currently VCD tracing can utilize
|
||||
at most "--trace-threads 1", and FST tracing can utilize at most
|
||||
"--trace-threads 2". This overrides :vlopt:`--no-threads` .
|
||||
in simulation runtime but uses more total compute. This option only
|
||||
applies to :vlopt:`--trace-fst`. FST tracing can utilize at most
|
||||
"--trace-threads 2". This overrides :vlopt:`--no-threads`.
|
||||
|
||||
This option is accepted, but has absolutely no effect with
|
||||
:vlopt:`--trace`, which respects :vlopt:`--threads` instead.
|
||||
|
||||
.. option:: --trace-underscore
|
||||
|
||||
|
|
@ -1676,9 +1719,28 @@ The grammar of configuration commands is as follows:
|
|||
|
||||
.. option:: tracing_off [-file "<filename>" [-lines <line> [ - <line> ]]]
|
||||
|
||||
Enable/disable waveform tracing for all future signals declared in the
|
||||
specified filename (or wildcard with '\*' or '?', or all files if
|
||||
omitted) and range of line numbers (or all lines if omitted).
|
||||
.. option:: tracing_on [-scope "<scopename>" [-levels <levels> ]]
|
||||
|
||||
For tracing_off, instances below any module in the files/ranges
|
||||
specified will also not be traced.
|
||||
.. option:: tracing_off [-scope "<scopename>" [-levels <levels> ]]
|
||||
|
||||
Enable/disable waveform tracing for all future signals declared in
|
||||
all files.
|
||||
|
||||
With -file, enable/disable waveform tracing in the specified
|
||||
filename (or wildcard with '\*' or '?'), and -line range of line
|
||||
numbers (or all lines if omitted).
|
||||
|
||||
For tracing_off with -file, instances below any module in the
|
||||
files/ranges specified will also not be traced. To overcome this
|
||||
feature, use tracing_on on the upper module declaration and on any
|
||||
cells, or use the -scope flavor of the command.
|
||||
|
||||
With -scope enable/disable waveform tracing for the specified scope (or
|
||||
wildcard with '\*' or '?'), and optional --levels number of levels
|
||||
below. These controls only take place after other file/line/module
|
||||
based controls have indicated the signal should be traced.
|
||||
|
||||
With -levels (used with -scope), the number of levels below that
|
||||
scope which the rule is to match, where 0 means all levels below, 1
|
||||
the exact level as the provided scope, and 2 meaning an additional
|
||||
level of children below the provided scope, etc.
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ or "`ifdef`"'s may break other tools.
|
|||
:option:`public_flat` signals.
|
||||
|
||||
To force a marked signal from C++, set the corresponding `__VforceVal`
|
||||
variable to the desired value, and the `__VforceEn` signal to the bitmask
|
||||
variable to the desired value, and the `__VforceEn` signal to the bit-mask
|
||||
indicating which bits of the signal to force. To force all bits of the
|
||||
target signal, set `__VforceEn` to all ones. To release the signal (or part
|
||||
thereof), set appropriate bits of the `__VforceEn` signal to zero.
|
||||
|
|
|
|||
|
|
@ -72,23 +72,38 @@ a good thing for getting working silicon.
|
|||
Will Verilator output remain under my own license/copyright?
|
||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Yes, it's just like using GCC on your programs; this is why Verilator uses
|
||||
the "GNU **Lesser** Public License Version 3" instead of the more typical
|
||||
"GNU Public License". See the licenses for details, but in brief, if you
|
||||
change Verilator itself or the header files Verilator includes, you must
|
||||
make the source code available under the GNU Lesser Public License.
|
||||
However, Verilator output (the Verilated code) only "include"s the licensed
|
||||
files, and so you are **not** required to open-source release any output
|
||||
from Verilator.
|
||||
Your SystemVerilog, VPI/DPI, or main() C++ code remains under your own license.
|
||||
|
||||
It's just like how using GCC on your programs does not change the copyright
|
||||
of your program; this is why Verilator uses the "GNU **Lesser** Public
|
||||
License Version 3" instead of the more typical "GNU Public License". See
|
||||
the licenses for details.
|
||||
|
||||
Some examples:
|
||||
|
||||
* Any SystemVerilog or other input fed into Verilator remain your own.
|
||||
|
||||
* Any of your VPI/DPI C++ routines that Verilator calls remain your own.
|
||||
|
||||
* Any of your main() C++ code that calls into Verilator remain your own.
|
||||
|
||||
* If you change Verilator itself, for example changing or adding a file
|
||||
under the src/ directory in the repository, you must make the source code
|
||||
available under the GNU Lesser Public License.
|
||||
|
||||
* If you change a header Verilator provides, for example under include/ in
|
||||
the repository, you must make the source code available under the GNU
|
||||
Lesser Public License.
|
||||
|
||||
You also have the option of using the Perl Artistic License, which again
|
||||
does not require you to release your Verilog or generated code, and also
|
||||
allows you to modify Verilator for internal use without distributing the
|
||||
modified version. But please contribute back to the community!
|
||||
does not require you to release your Verilog, C++, or generated code. This
|
||||
license also allows you to modify Verilator for internal use without
|
||||
distributing the modified version. But please contribute back to the
|
||||
community!
|
||||
|
||||
One limit is that you cannot under either license release a closed-source
|
||||
Verilog simulation product incorporating Verilator. That is you can have a
|
||||
commercial product, but must make the source code available.
|
||||
Under both license you can offer a commercial product that is based on
|
||||
Verilator either directly or embedded within. However under both licenses,
|
||||
any changes you make to Verilator for such a product must be open sourced.
|
||||
|
||||
As is standard with Open Source, contributions back to Verilator will be
|
||||
placed under the Verilator copyright and LGPL/Artistic license. Small test
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ compiles under all the options above, plus using MSVC++.
|
|||
Install Prerequisites
|
||||
---------------------
|
||||
|
||||
To build or run Verilator you need these standard packages:
|
||||
To build or run Verilator, you need these standard packages:
|
||||
|
||||
::
|
||||
|
||||
|
|
@ -98,13 +98,20 @@ To build or run Verilator you need these standard packages:
|
|||
sudo apt-get install libfl-dev # Ubuntu only (ignore if gives error)
|
||||
sudo apt-get install zlibc zlib1g zlib1g-dev # Ubuntu only (ignore if gives error)
|
||||
|
||||
To build or run the following are optional but should be installed for good
|
||||
performance:
|
||||
To build or run Verilator, the following are optional but should be installed
|
||||
for good performance:
|
||||
|
||||
::
|
||||
|
||||
sudo apt-get install ccache # If present at build, needed for run
|
||||
sudo apt-get install libgoogle-perftools-dev numactl perl-doc
|
||||
sudo apt-get install libgoogle-perftools-dev numactl
|
||||
|
||||
The following is optional but is recommended for nicely rendered command line
|
||||
help when running Verilator:
|
||||
|
||||
::
|
||||
|
||||
sudo apt-get install perl-doc
|
||||
|
||||
To build Verilator you will need to install these packages; these do not
|
||||
need to be present to run Verilator:
|
||||
|
|
@ -118,7 +125,8 @@ Those developing Verilator itself may also want these (see internals.rst):
|
|||
::
|
||||
|
||||
sudo apt-get install gdb graphviz cmake clang clang-format-11 gprof lcov
|
||||
sudo pip3 install sphinx sphinx_rtd_theme breathe
|
||||
sudo apt-get install yapf3
|
||||
sudo pip3 install sphinx sphinx_rtd_theme sphinxcontrib-spelling breathe
|
||||
cpan install Pod::Perldoc
|
||||
cpan install Parallel::Forker
|
||||
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ Verilator supports the procedural `force` (and corresponding `release`)
|
|||
statement. The behavior of the `force` statement however does not entirely
|
||||
comply with the IEEE 1800 SystemVerilog standard. According to the standard,
|
||||
when a procedural statement of the form `force a = b;` is executed, the
|
||||
simulation should behave as if from that point onwards, a continuous
|
||||
simulation should behave as if from that point forwards, a continuous
|
||||
assignment `assign a = b;` have been added to override the drivers of `a`.
|
||||
More specifically: the value of `a` should be updated, whenever the value of
|
||||
`b` changes, all the way until a `release a;` statement is executed.
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ others as they prove beneficial.
|
|||
Thread Profile-Guided Optimization
|
||||
----------------------------------
|
||||
|
||||
Verilator supports profile-guided optimization (verilation) of multi-threaded
|
||||
Verilator supports profile-guided optimization (Verilation) of multi-threaded
|
||||
models (Thread PGO) to improve performance.
|
||||
|
||||
When using multithreading, Verilator computes how long macro tasks take and
|
||||
|
|
|
|||
|
|
@ -221,9 +221,13 @@ model, it may be beneficial to performance to adjust the
|
|||
influences the partitioning of the model by adjusting the assumed execution
|
||||
time of DPI imports.
|
||||
|
||||
The :vlopt:`--trace-threads` options can be used to produce trace dumps
|
||||
using multiple threads. If :vlopt:`--trace-threads` is set without
|
||||
:vlopt:`--threads`, then :vlopt:`--trace-threads` will imply
|
||||
When using :vlopt:`--trace` to perform VCD tracing, the VCD trace
|
||||
construction is parallelized using the same number of threads as specified
|
||||
with :vlopt:`--threads`, and is executed on the same thread pool as the model.
|
||||
|
||||
The :vlopt:`--trace-threads` options can be used with :vlopt:`--trace-fst`
|
||||
to offload FST tracing using multiple threads. If :vlopt:`--trace-threads` is
|
||||
given without :vlopt:`--threads`, then :vlopt:`--trace-threads` will imply
|
||||
:vlopt:`--threads 1 <--threads>`, i.e.: the support libraries will be
|
||||
thread safe.
|
||||
|
||||
|
|
@ -231,12 +235,12 @@ With :vlopt:`--trace-threads 0 <--trace-threads>`, trace dumps are produced
|
|||
on the main thread. This again gives the highest single thread performance.
|
||||
|
||||
With :vlopt:`--trace-threads {N} <--trace-threads>`, where N is at least 1,
|
||||
N additional threads will be created and managed by the trace files (e.g.:
|
||||
VerilatedVcdC or VerilatedFstC), to generate the trace dump. The main
|
||||
thread will be released to proceed with execution as soon as possible,
|
||||
though some blocking of the main thread is still necessary while capturing
|
||||
the trace. Different trace formats can utilize a various number of
|
||||
threads. See the :vlopt:`--trace-threads` option.
|
||||
up to N additional threads will be created and managed by the trace files
|
||||
(e.g.: VerilatedFstC), to offload construction of the trace dump. The main
|
||||
thread will be released to proceed with execution as soon as possible, though
|
||||
some blocking of the main thread is still necessary while capturing the
|
||||
trace. FST tracing can utilize up to 2 offload threads, so there is no use
|
||||
of setting :vlopt:`--trace-threads` higher than 2 at the moment.
|
||||
|
||||
When running a multithreaded model, the default Linux task scheduler often
|
||||
works against the model, by assuming threads are short lived, and thus
|
||||
|
|
@ -441,7 +445,7 @@ SystemC include directories and link to the SystemC libraries.
|
|||
|
||||
.. describe:: TRACE_THREADS
|
||||
|
||||
Optional. Generated multi-threaded trace dumping, same as
|
||||
Optional. Generated multi-threaded FST trace dumping, same as
|
||||
"--trace-threads".
|
||||
|
||||
.. describe:: TOP_MODULE
|
||||
|
|
|
|||
|
|
@ -1506,7 +1506,7 @@ List Of Warnings
|
|||
Error that a construct might be legal according to IEEE but is not
|
||||
currently supported by Verilator.
|
||||
|
||||
A typical workaround is to recode the construct into a simpler and more
|
||||
A typical workaround is to rewrite the construct into a simpler and more
|
||||
common alternative language construct.
|
||||
|
||||
Alternatively, check if the construct is supported by other tools, and
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ path through the graph is the sum of macro-task execution costs. Sarkar
|
|||
does almost the same thing, except that he has nonzero estimates for
|
||||
synchronization costs.
|
||||
|
||||
Verilator's cost estimates are assigned by ``InstrCountCostVisitor``. This
|
||||
Verilator's cost estimates are assigned by ``InstrCountVisitor``. This
|
||||
class is perhaps the most fragile piece of the multithread
|
||||
implementation. It's easy to have a bug where you count something cheap
|
||||
(eg. accessing one element of a huge array) as if it were expensive (eg.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ ABCp
|
|||
Accellera
|
||||
Affe
|
||||
Aleksander
|
||||
Alexandre
|
||||
Ami
|
||||
Amir
|
||||
Anastasiadis
|
||||
Anikin
|
||||
Antonin
|
||||
Antwerpen
|
||||
Arasanipalai
|
||||
|
|
@ -70,6 +72,7 @@ Eda
|
|||
Eddleston
|
||||
Egbert
|
||||
Egil
|
||||
Ehab
|
||||
Eiler
|
||||
Eivind
|
||||
El
|
||||
|
|
@ -82,6 +85,7 @@ Fabrizio
|
|||
Fekete
|
||||
Ferrandi
|
||||
Flachs
|
||||
Flavien
|
||||
Foletto
|
||||
Forker
|
||||
Francillon
|
||||
|
|
@ -104,6 +108,7 @@ Goessling
|
|||
Gonnen
|
||||
Goorah
|
||||
Gossner
|
||||
Graybeal
|
||||
Grobman
|
||||
Gunter
|
||||
Guo
|
||||
|
|
@ -121,6 +126,7 @@ Hornung
|
|||
Hossell
|
||||
Hsu
|
||||
Hyperthreading
|
||||
Ibrahim
|
||||
Iles
|
||||
Inlines
|
||||
Inout
|
||||
|
|
@ -134,6 +140,7 @@ Jannis
|
|||
Jens
|
||||
Jeras
|
||||
Jiuyang
|
||||
Joannou
|
||||
Joly
|
||||
Jullien
|
||||
Junji
|
||||
|
|
@ -147,6 +154,7 @@ Kamendje
|
|||
Kandadi
|
||||
Kaplan
|
||||
Karge
|
||||
Karlsson
|
||||
Katz
|
||||
Katzman
|
||||
Keren
|
||||
|
|
@ -161,6 +169,7 @@ Kolecki
|
|||
Koonce
|
||||
Korteland
|
||||
Koszek
|
||||
Kouping
|
||||
Kravitz
|
||||
Krolnik
|
||||
Kruse
|
||||
|
|
@ -194,6 +203,7 @@ Mednick
|
|||
Michiels
|
||||
Microsystems
|
||||
Milanovic
|
||||
Millis
|
||||
MinW
|
||||
Mindspeed
|
||||
Miodrag
|
||||
|
|
@ -224,12 +234,14 @@ Petr
|
|||
Piechotka
|
||||
Piersall
|
||||
Plunkett
|
||||
Popolon
|
||||
Popov
|
||||
Prabhat
|
||||
Prabhu
|
||||
Prateek
|
||||
Pre
|
||||
Preprocess
|
||||
Pretet
|
||||
Priyadharshini
|
||||
Pullup
|
||||
Pulver
|
||||
|
|
@ -262,12 +274,15 @@ Shi
|
|||
Shinkarovsky
|
||||
Shirakawa
|
||||
Shuba
|
||||
Shunyao
|
||||
Slatter
|
||||
SoC
|
||||
Sobhan
|
||||
Sokorac
|
||||
Solaris
|
||||
Solomatnikov
|
||||
Solt
|
||||
Southwell
|
||||
Srini
|
||||
Stamness
|
||||
Stephane
|
||||
|
|
@ -286,6 +301,7 @@ SystemVerilog
|
|||
Takatsukasa
|
||||
Tambe
|
||||
Tariq
|
||||
Tejada
|
||||
Tengstrand
|
||||
Terpstra
|
||||
Thiede
|
||||
|
|
@ -293,8 +309,10 @@ Thierry
|
|||
Thyer
|
||||
Tichelaar
|
||||
Tomov
|
||||
Tood
|
||||
Topa
|
||||
Tota
|
||||
Trefor
|
||||
Tresidder
|
||||
Tri
|
||||
Tristate
|
||||
|
|
@ -304,11 +322,13 @@ Ubixum
|
|||
Uints
|
||||
Undefines
|
||||
Unsized
|
||||
Urbach
|
||||
Uselib
|
||||
Usha
|
||||
Usuario
|
||||
VERILATOR
|
||||
Vasu
|
||||
Vdeeptemp
|
||||
Vdly
|
||||
Vemumtab
|
||||
Vemuri
|
||||
|
|
@ -351,7 +371,9 @@ Zhang
|
|||
abirkmanis
|
||||
accessor
|
||||
accessors
|
||||
adrienlemasle
|
||||
agrobman
|
||||
ahouska
|
||||
al
|
||||
ala
|
||||
andit
|
||||
|
|
@ -372,8 +394,12 @@ backtrace
|
|||
backtraces
|
||||
basename
|
||||
bbox
|
||||
benchmarking
|
||||
biguint
|
||||
biops
|
||||
bitOpTree
|
||||
bitOpTree
|
||||
bitop
|
||||
bitstoreal
|
||||
blackbox
|
||||
bokke
|
||||
|
|
@ -389,6 +415,7 @@ casez
|
|||
casted
|
||||
cb
|
||||
ccache
|
||||
ccall
|
||||
cdc
|
||||
ceil
|
||||
celldefine
|
||||
|
|
@ -411,6 +438,8 @@ concat
|
|||
concats
|
||||
config
|
||||
const
|
||||
constexpr
|
||||
constpool
|
||||
coredump
|
||||
countbits
|
||||
countones
|
||||
|
|
@ -426,6 +455,7 @@ da
|
|||
dat
|
||||
datadir
|
||||
datafiles
|
||||
david
|
||||
ddd
|
||||
deassign
|
||||
debugi
|
||||
|
|
@ -434,6 +464,7 @@ defname
|
|||
defparam
|
||||
demangling
|
||||
der
|
||||
dereference
|
||||
desassign
|
||||
destructor
|
||||
detections
|
||||
|
|
@ -462,6 +493,7 @@ dumpvars
|
|||
dut
|
||||
dx
|
||||
elab
|
||||
elike
|
||||
elsif
|
||||
endcase
|
||||
endcelldefine
|
||||
|
|
@ -480,15 +512,18 @@ enums
|
|||
env
|
||||
envvar
|
||||
eof
|
||||
errae
|
||||
erroring
|
||||
et
|
||||
eval
|
||||
evals
|
||||
exe
|
||||
executables
|
||||
expr
|
||||
extern
|
||||
fanin
|
||||
fasttrace
|
||||
fauto
|
||||
fbranch
|
||||
fclose
|
||||
fdisplay
|
||||
|
|
@ -503,6 +538,7 @@ filt
|
|||
flto
|
||||
flushCall
|
||||
fopen
|
||||
forceable
|
||||
foreach
|
||||
fprintf
|
||||
fprofile
|
||||
|
|
@ -521,6 +557,7 @@ funcs
|
|||
fwrite
|
||||
gantt
|
||||
gcc
|
||||
gcda
|
||||
gdb
|
||||
genblk
|
||||
genvar
|
||||
|
|
@ -552,6 +589,7 @@ incdir
|
|||
includer
|
||||
inferfaces
|
||||
inhibitSim
|
||||
initarray
|
||||
initializer
|
||||
initializers
|
||||
inits
|
||||
|
|
@ -570,6 +608,7 @@ killua
|
|||
lang
|
||||
lcov
|
||||
ld
|
||||
leavinel
|
||||
len
|
||||
libext
|
||||
libgoogle
|
||||
|
|
@ -584,6 +623,7 @@ ln
|
|||
loc
|
||||
localparam
|
||||
localparams
|
||||
localtime
|
||||
logicals
|
||||
longint
|
||||
lsb
|
||||
|
|
@ -618,6 +658,7 @@ multithreaded
|
|||
multithreading
|
||||
mutexes
|
||||
mux
|
||||
myftptoyman
|
||||
mysignal
|
||||
namespace
|
||||
nand
|
||||
|
|
@ -642,15 +683,19 @@ onehot
|
|||
ooo
|
||||
oprofile
|
||||
oversubscription
|
||||
parallelized
|
||||
param
|
||||
parameterized
|
||||
params
|
||||
parens
|
||||
pawel
|
||||
pc
|
||||
pdf
|
||||
perf
|
||||
perftools
|
||||
pgo
|
||||
picoChip
|
||||
pinIndex
|
||||
pinout
|
||||
plusargs
|
||||
pmos
|
||||
|
|
@ -676,6 +721,7 @@ prev
|
|||
printf
|
||||
printtimescale
|
||||
profcfunc
|
||||
profiler
|
||||
prototyptes
|
||||
ps
|
||||
pthread
|
||||
|
|
@ -703,12 +749,14 @@ regs
|
|||
reloop
|
||||
resetall
|
||||
respecified
|
||||
rodata
|
||||
rr
|
||||
rst
|
||||
runtime
|
||||
runtimes
|
||||
rw
|
||||
sVerilator
|
||||
sawatzke
|
||||
sc
|
||||
scalared
|
||||
sccanf
|
||||
|
|
@ -724,6 +772,7 @@ specparam
|
|||
splitme
|
||||
spp
|
||||
sqrt
|
||||
src
|
||||
srcdir
|
||||
srcfile
|
||||
sscanf
|
||||
|
|
@ -743,6 +792,7 @@ subcells
|
|||
subexpressions
|
||||
submodule
|
||||
submodules
|
||||
substring
|
||||
sv
|
||||
svBitVal
|
||||
svBitVecVal
|
||||
|
|
@ -777,6 +827,7 @@ trunc
|
|||
txt
|
||||
typ
|
||||
typedef
|
||||
typedef'ed
|
||||
typedefed
|
||||
typedefs
|
||||
typename
|
||||
|
|
@ -790,6 +841,7 @@ uniquified
|
|||
unistd
|
||||
unlink
|
||||
unlinked
|
||||
unnamedblk
|
||||
unopt
|
||||
unoptflat
|
||||
unoptimizable
|
||||
|
|
@ -839,11 +891,14 @@ writeme
|
|||
writemem
|
||||
writememb
|
||||
writememh
|
||||
xiak
|
||||
xin
|
||||
xml
|
||||
xnor
|
||||
xout
|
||||
xuejiazidi
|
||||
yanx
|
||||
ypq
|
||||
yurivict
|
||||
zdave
|
||||
Øyvind
|
||||
|
|
|
|||
|
|
@ -33,5 +33,5 @@ add_executable(example ../make_tracing_c/sim_main.cpp)
|
|||
# Add the Verilated circuit to the target
|
||||
verilate(example COVERAGE TRACE
|
||||
INCLUDE_DIRS "../make_tracing_c"
|
||||
VERILATOR_ARGS -f ../make_tracing_c/input.vc -Os -x-assign 0
|
||||
VERILATOR_ARGS -f ../make_tracing_c/input.vc -x-assign fast
|
||||
SOURCES ../make_tracing_c/top.v)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ set_property(
|
|||
# Add the Verilated circuit to the target
|
||||
verilate(example SYSTEMC COVERAGE TRACE
|
||||
INCLUDE_DIRS "../make_tracing_sc"
|
||||
VERILATOR_ARGS -f ../make_tracing_sc/input.vc -Os -x-assign 0
|
||||
VERILATOR_ARGS -f ../make_tracing_sc/input.vc -x-assign fast
|
||||
SOURCES ../make_tracing_sc/top.v)
|
||||
|
||||
verilator_link_systemc(example)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ VERILATOR_FLAGS =
|
|||
# Generate C++
|
||||
VERILATOR_FLAGS += -cc
|
||||
# Optimize
|
||||
VERILATOR_FLAGS += -Os -x-assign 0
|
||||
VERILATOR_FLAGS += -x-assign fast
|
||||
# Warn abount lint issues; may not want this on less solid designs
|
||||
VERILATOR_FLAGS += -Wall
|
||||
# This example does not use vl_time_stamp but rather
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ VERILATOR_FLAGS += -cc --exe
|
|||
# Generate makefile dependencies (not shown as complicates the Makefile)
|
||||
#VERILATOR_FLAGS += -MMD
|
||||
# Optimize
|
||||
VERILATOR_FLAGS += -Os -x-assign 0
|
||||
VERILATOR_FLAGS += -x-assign fast
|
||||
# Warn abount lint issues; may not want this on less solid designs
|
||||
VERILATOR_FLAGS += -Wall
|
||||
# Make waveforms
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ int main(int argc, char** argv, char** env) {
|
|||
// Using unique_ptr is similar to
|
||||
// "VerilatedContext* contextp = new VerilatedContext" then deleting at end.
|
||||
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
|
||||
// Do not instead make Vtop as a file-scope static variable, as the
|
||||
// "C++ static initialization order fiasco" may cause a crash
|
||||
|
||||
// Set debug level, 0 is off, 9 is highest presently used
|
||||
// May be overridden by commandArgs argument parsing
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ VERILATOR_FLAGS += -sc --exe
|
|||
# Generate makefile dependencies (not shown as complicates the Makefile)
|
||||
#VERILATOR_FLAGS += -MMD
|
||||
# Optimize
|
||||
VERILATOR_FLAGS += -Os -x-assign 0
|
||||
VERILATOR_FLAGS += -x-assign fast
|
||||
# Warn abount lint issues; may not want this on less solid designs
|
||||
VERILATOR_FLAGS += -Wall
|
||||
# Make waveforms
|
||||
|
|
|
|||
|
|
@ -2289,7 +2289,17 @@ VerilatedContext::VerilatedContext()
|
|||
}
|
||||
|
||||
// Must declare here not in interface, as otherwise forward declarations not known
|
||||
VerilatedContext::~VerilatedContext() {}
|
||||
VerilatedContext::~VerilatedContext() {
|
||||
checkMagic(this);
|
||||
m_magic = 0x1; // Arbitrary but 0x1 is what Verilator src uses for a deleted pointer
|
||||
}
|
||||
|
||||
void VerilatedContext::checkMagic(const VerilatedContext* contextp) {
|
||||
if (VL_UNLIKELY(!contextp || contextp->m_magic != MAGIC)) {
|
||||
VL_FATAL_MT("", 0, "", // LCOV_EXCL_LINE
|
||||
"Attempt to create model using a bad/deleted VerilatedContext pointer");
|
||||
}
|
||||
}
|
||||
|
||||
VerilatedContext::Serialized::Serialized() {
|
||||
m_timeunit = VL_TIME_UNIT; // Initial value until overriden by _Vconfigure
|
||||
|
|
@ -2657,6 +2667,7 @@ const VerilatedScopeNameMap* VerilatedContext::scopeNameMap() VL_MT_SAFE {
|
|||
|
||||
VerilatedSyms::VerilatedSyms(VerilatedContext* contextp)
|
||||
: _vm_contextp__(contextp ? contextp : Verilated::threadContextp()) {
|
||||
VerilatedContext::checkMagic(_vm_contextp__);
|
||||
Verilated::threadContextp(_vm_contextp__);
|
||||
#ifdef VL_THREADED
|
||||
__Vm_evalMsgQp = new VerilatedEvalMsgQueue;
|
||||
|
|
@ -2664,6 +2675,7 @@ VerilatedSyms::VerilatedSyms(VerilatedContext* contextp)
|
|||
}
|
||||
|
||||
VerilatedSyms::~VerilatedSyms() {
|
||||
VerilatedContext::checkMagic(_vm_contextp__);
|
||||
#ifdef VL_THREADED
|
||||
delete __Vm_evalMsgQp;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ extern uint32_t VL_THREAD_ID() VL_MT_SAFE;
|
|||
|
||||
#if VL_THREADED
|
||||
|
||||
#define VL_LOCK_SPINS 50000 /// Number of times to spin for a mutex before relaxing
|
||||
#define VL_LOCK_SPINS 50000 /// Number of times to spin for a mutex before yielding
|
||||
|
||||
/// Mutex, wrapped to allow -fthread_safety checks
|
||||
class VL_CAPABILITY("mutex") VerilatedMutex final {
|
||||
|
|
@ -374,6 +374,10 @@ protected:
|
|||
// List of free descriptors in the MCT region [4, 32)
|
||||
std::vector<IData> m_fdFreeMct VL_GUARDED_BY(m_fdMutex);
|
||||
|
||||
// Magic to check for bad construction
|
||||
static constexpr uint64_t MAGIC = 0xC35F9A6E5298EE6EULL; // SHA256 "VerilatedContext"
|
||||
uint64_t m_magic = MAGIC;
|
||||
|
||||
private:
|
||||
// CONSTRUCTORS
|
||||
VL_UNCOPYABLE(VerilatedContext);
|
||||
|
|
@ -535,6 +539,10 @@ public: // But for internal use only
|
|||
// Internal: Serialization setup
|
||||
static constexpr size_t serialized1Size() VL_PURE { return sizeof(m_s); }
|
||||
void* serialized1Ptr() VL_MT_UNSAFE { return &m_s; }
|
||||
|
||||
// Internal: Check magic number
|
||||
static void checkMagic(const VerilatedContext* contextp);
|
||||
void selfTestClearMagic() { m_magic = 0x2; }
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ VK_CPPFLAGS_ALWAYS += \
|
|||
-DVM_SC=$(VM_SC) \
|
||||
-DVM_TRACE=$(VM_TRACE) \
|
||||
-DVM_TRACE_FST=$(VM_TRACE_FST) \
|
||||
-DVM_TRACE_VCD=$(VM_TRACE_VCD) \
|
||||
$(CFG_CXXFLAGS_NO_UNUSED) \
|
||||
|
||||
ifeq ($(CFG_WITH_CCWARN),yes) # Local... Else don't burden users
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ private:
|
|||
using ItemList = std::deque<VerilatedCovImpItem*>;
|
||||
|
||||
// MEMBERS
|
||||
VerilatedMutex m_mutex; // Protects all members
|
||||
mutable VerilatedMutex m_mutex; // Protects all members
|
||||
ValueIndexMap m_valueIndexes VL_GUARDED_BY(m_mutex); // Unique arbitrary value for values
|
||||
IndexValueMap m_indexValues VL_GUARDED_BY(m_mutex); // Unique arbitrary value for keys
|
||||
ItemList m_items VL_GUARDED_BY(m_mutex); // List of all items
|
||||
|
|
@ -123,10 +123,6 @@ public:
|
|||
protected:
|
||||
friend class VerilatedCovContext;
|
||||
virtual ~VerilatedCovImp() override { clearGuts(); }
|
||||
static VerilatedCovImp& imp() VL_MT_SAFE {
|
||||
static VerilatedCovImp s_singleton;
|
||||
return s_singleton;
|
||||
}
|
||||
|
||||
private:
|
||||
// PRIVATE METHODS
|
||||
|
|
|
|||
|
|
@ -157,8 +157,8 @@ protected:
|
|||
friend class VerilatedCovImp;
|
||||
// CONSTRUCTORS
|
||||
// Internal: Only made as part of VerilatedCovImp
|
||||
VerilatedCovContext() {}
|
||||
virtual ~VerilatedCovContext() {}
|
||||
VerilatedCovContext() = default;
|
||||
virtual ~VerilatedCovContext() = default;
|
||||
|
||||
// METHODS
|
||||
// Internal: access to implementation class
|
||||
|
|
|
|||
|
|
@ -83,9 +83,11 @@ static_assert(static_cast<int>(FST_ST_VCD_PROGRAM) == static_cast<int>(VLT_TRACE
|
|||
//=============================================================================
|
||||
// Specialization of the generics for this trace format
|
||||
|
||||
#define VL_DERIVED_T VerilatedFst
|
||||
#include "verilated_trace_imp.cpp"
|
||||
#undef VL_DERIVED_T
|
||||
#define VL_SUB_T VerilatedFst
|
||||
#define VL_BUF_T VerilatedFstBuffer
|
||||
#include "verilated_trace_imp.h"
|
||||
#undef VL_SUB_T
|
||||
#undef VL_BUF_T
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedFst
|
||||
|
|
@ -111,7 +113,7 @@ void VerilatedFst::open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|||
|
||||
m_curScope.clear();
|
||||
|
||||
VerilatedTrace<VerilatedFst>::traceInit();
|
||||
Super::traceInit();
|
||||
|
||||
// Clear the scope stack
|
||||
auto it = m_curScope.begin();
|
||||
|
|
@ -133,14 +135,14 @@ void VerilatedFst::open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|||
|
||||
void VerilatedFst::close() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
||||
const VerilatedLockGuard lock{m_mutex};
|
||||
VerilatedTrace<VerilatedFst>::closeBase();
|
||||
Super::closeBase();
|
||||
fstWriterClose(m_fst);
|
||||
m_fst = nullptr;
|
||||
}
|
||||
|
||||
void VerilatedFst::flush() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
||||
const VerilatedLockGuard lock{m_mutex};
|
||||
VerilatedTrace<VerilatedFst>::flushBase();
|
||||
Super::flushBase();
|
||||
fstWriterFlushContext(m_fst);
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +164,7 @@ void VerilatedFst::declare(uint32_t code, const char* name, int dtypenum, fstVar
|
|||
int lsb) {
|
||||
const int bits = ((msb > lsb) ? (msb - lsb) : (lsb - msb)) + 1;
|
||||
|
||||
const bool enabled = VerilatedTrace<VerilatedFst>::declCode(code, name, bits, false);
|
||||
const bool enabled = Super::declCode(code, name, bits, false);
|
||||
if (!enabled) return;
|
||||
|
||||
std::string nameasstr = namePrefix() + name;
|
||||
|
|
@ -245,18 +247,42 @@ void VerilatedFst::declDouble(uint32_t code, const char* name, int dtypenum, fst
|
|||
declare(code, name, dtypenum, vardir, vartype, array, arraynum, false, 63, 0);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Get/commit trace buffer
|
||||
|
||||
VerilatedFstBuffer* VerilatedFst::getTraceBuffer() { return new VerilatedFstBuffer{*this}; }
|
||||
|
||||
void VerilatedFst::commitTraceBuffer(VerilatedFstBuffer* bufp) {
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
if (bufp->m_offloadBufferWritep) {
|
||||
m_offloadBufferWritep = bufp->m_offloadBufferWritep;
|
||||
return; // Buffer will be deleted by the offload thread
|
||||
}
|
||||
#endif
|
||||
delete bufp;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedFstBuffer implementation
|
||||
|
||||
VerilatedFstBuffer::VerilatedFstBuffer(VerilatedFst& owner)
|
||||
: VerilatedTraceBuffer<VerilatedFst, VerilatedFstBuffer>{owner} {}
|
||||
|
||||
//=============================================================================
|
||||
// Trace rendering primitives
|
||||
|
||||
// Note: emit* are only ever called from one place (full* in
|
||||
// verilated_trace_imp.cpp, which is included in this file at the top),
|
||||
// verilated_trace_imp.h, which is included in this file at the top),
|
||||
// so always inline them.
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedFst::emitBit(uint32_t code, CData newval) {
|
||||
void VerilatedFstBuffer::emitBit(uint32_t code, CData newval) {
|
||||
VL_DEBUG_IFDEF(assert(m_symbolp[code]););
|
||||
fstWriterEmitValueChange(m_fst, m_symbolp[code], newval ? "1" : "0");
|
||||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedFst::emitCData(uint32_t code, CData newval, int bits) {
|
||||
void VerilatedFstBuffer::emitCData(uint32_t code, CData newval, int bits) {
|
||||
char buf[VL_BYTESIZE];
|
||||
VL_DEBUG_IFDEF(assert(m_symbolp[code]););
|
||||
cvtCDataToStr(buf, newval << (VL_BYTESIZE - bits));
|
||||
|
|
@ -264,7 +290,7 @@ void VerilatedFst::emitCData(uint32_t code, CData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedFst::emitSData(uint32_t code, SData newval, int bits) {
|
||||
void VerilatedFstBuffer::emitSData(uint32_t code, SData newval, int bits) {
|
||||
char buf[VL_SHORTSIZE];
|
||||
VL_DEBUG_IFDEF(assert(m_symbolp[code]););
|
||||
cvtSDataToStr(buf, newval << (VL_SHORTSIZE - bits));
|
||||
|
|
@ -272,7 +298,7 @@ void VerilatedFst::emitSData(uint32_t code, SData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedFst::emitIData(uint32_t code, IData newval, int bits) {
|
||||
void VerilatedFstBuffer::emitIData(uint32_t code, IData newval, int bits) {
|
||||
char buf[VL_IDATASIZE];
|
||||
VL_DEBUG_IFDEF(assert(m_symbolp[code]););
|
||||
cvtIDataToStr(buf, newval << (VL_IDATASIZE - bits));
|
||||
|
|
@ -280,7 +306,7 @@ void VerilatedFst::emitIData(uint32_t code, IData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedFst::emitQData(uint32_t code, QData newval, int bits) {
|
||||
void VerilatedFstBuffer::emitQData(uint32_t code, QData newval, int bits) {
|
||||
char buf[VL_QUADSIZE];
|
||||
VL_DEBUG_IFDEF(assert(m_symbolp[code]););
|
||||
cvtQDataToStr(buf, newval << (VL_QUADSIZE - bits));
|
||||
|
|
@ -288,7 +314,7 @@ void VerilatedFst::emitQData(uint32_t code, QData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedFst::emitWData(uint32_t code, const WData* newvalp, int bits) {
|
||||
void VerilatedFstBuffer::emitWData(uint32_t code, const WData* newvalp, int bits) {
|
||||
int words = VL_WORDS_I(bits);
|
||||
char* wp = m_strbuf;
|
||||
// Convert the most significant word
|
||||
|
|
@ -304,6 +330,6 @@ void VerilatedFst::emitWData(uint32_t code, const WData* newvalp, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedFst::emitDouble(uint32_t code, double newval) {
|
||||
void VerilatedFstBuffer::emitDouble(uint32_t code, double newval) {
|
||||
fstWriterEmitValueChange(m_fst, m_symbolp[code], &newval);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,15 +31,19 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class VerilatedFstBuffer;
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedFst
|
||||
// Base class to create a Verilator FST dump
|
||||
// This is an internally used class - see VerilatedFstC for what to call from applications
|
||||
|
||||
class VerilatedFst final : public VerilatedTrace<VerilatedFst> {
|
||||
class VerilatedFst final : public VerilatedTrace<VerilatedFst, VerilatedFstBuffer> {
|
||||
public:
|
||||
using Super = VerilatedTrace<VerilatedFst, VerilatedFstBuffer>;
|
||||
|
||||
private:
|
||||
// Give the superclass access to private bits (to avoid virtual functions)
|
||||
friend class VerilatedTrace<VerilatedFst>;
|
||||
friend Buffer; // Give the buffer access to the private bits
|
||||
|
||||
//=========================================================================
|
||||
// FST specific internals
|
||||
|
|
@ -60,31 +64,26 @@ protected:
|
|||
//=========================================================================
|
||||
// Implementation of VerilatedTrace interface
|
||||
|
||||
// Implementations of protected virtual methods for VerilatedTrace
|
||||
// Called when the trace moves forward to a new time point
|
||||
virtual void emitTimeChange(uint64_t timeui) override;
|
||||
|
||||
// Hooks called from VerilatedTrace
|
||||
virtual bool preFullDump() override { return isOpen(); }
|
||||
virtual bool preChangeDump() override { return isOpen(); }
|
||||
|
||||
// Implementations of duck-typed methods for VerilatedTrace. These are
|
||||
// called from only one place (namely full*) so always inline them.
|
||||
inline void emitBit(uint32_t code, CData newval);
|
||||
inline void emitCData(uint32_t code, CData newval, int bits);
|
||||
inline void emitSData(uint32_t code, SData newval, int bits);
|
||||
inline void emitIData(uint32_t code, IData newval, int bits);
|
||||
inline void emitQData(uint32_t code, QData newval, int bits);
|
||||
inline void emitWData(uint32_t code, const WData* newvalp, int bits);
|
||||
inline void emitDouble(uint32_t code, double newval);
|
||||
// Trace buffer management
|
||||
virtual VerilatedFstBuffer* getTraceBuffer() override;
|
||||
virtual void commitTraceBuffer(VerilatedFstBuffer*) override;
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// External interface to client code
|
||||
// (All must be threadsafe)
|
||||
|
||||
// CONSTRUCTOR
|
||||
explicit VerilatedFst(void* fst = nullptr);
|
||||
~VerilatedFst();
|
||||
|
||||
// METHODS - All must be thread safe
|
||||
// Open the file; call isOpen() to see if errors
|
||||
void open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex);
|
||||
// Close the file
|
||||
|
|
@ -97,11 +96,6 @@ public:
|
|||
//=========================================================================
|
||||
// Internal interface to Verilator generated code
|
||||
|
||||
// Inside dumping routines, declare a data type
|
||||
void declDTypeEnum(int dtypenum, const char* name, uint32_t elements, unsigned int minValbits,
|
||||
const char** itemNamesp, const char** itemValuesp);
|
||||
|
||||
// Inside dumping routines, declare a signal
|
||||
void declBit(uint32_t code, const char* name, int dtypenum, fstVarDir vardir,
|
||||
fstVarType vartype, bool array, int arraynum);
|
||||
void declBus(uint32_t code, const char* name, int dtypenum, fstVarDir vardir,
|
||||
|
|
@ -112,18 +106,55 @@ public:
|
|||
fstVarType vartype, bool array, int arraynum, int msb, int lsb);
|
||||
void declDouble(uint32_t code, const char* name, int dtypenum, fstVarDir vardir,
|
||||
fstVarType vartype, bool array, int arraynum);
|
||||
|
||||
void declDTypeEnum(int dtypenum, const char* name, uint32_t elements, unsigned int minValbits,
|
||||
const char** itemNamesp, const char** itemValuesp);
|
||||
};
|
||||
|
||||
#ifndef DOXYGEN
|
||||
// Declare specialization here as it's used in VerilatedFstC just below
|
||||
template <> void VerilatedTrace<VerilatedFst>::dump(uint64_t timeui);
|
||||
template <> void VerilatedTrace<VerilatedFst>::set_time_unit(const char* unitp);
|
||||
template <> void VerilatedTrace<VerilatedFst>::set_time_unit(const std::string& unit);
|
||||
template <> void VerilatedTrace<VerilatedFst>::set_time_resolution(const char* unitp);
|
||||
template <> void VerilatedTrace<VerilatedFst>::set_time_resolution(const std::string& unit);
|
||||
template <> void VerilatedTrace<VerilatedFst>::dumpvars(int level, const std::string& hier);
|
||||
template <> void VerilatedFst::Super::dump(uint64_t time);
|
||||
template <> void VerilatedFst::Super::set_time_unit(const char* unitp);
|
||||
template <> void VerilatedFst::Super::set_time_unit(const std::string& unit);
|
||||
template <> void VerilatedFst::Super::set_time_resolution(const char* unitp);
|
||||
template <> void VerilatedFst::Super::set_time_resolution(const std::string& unit);
|
||||
template <> void VerilatedFst::Super::dumpvars(int level, const std::string& hier);
|
||||
#endif
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedFstBuffer
|
||||
|
||||
class VerilatedFstBuffer final : public VerilatedTraceBuffer<VerilatedFst, VerilatedFstBuffer> {
|
||||
// Give the trace file access to the private bits
|
||||
friend VerilatedFst;
|
||||
friend VerilatedFst::Super;
|
||||
|
||||
// The FST file handle
|
||||
void* const m_fst = m_owner.m_fst;
|
||||
// code to fstHande map, as an array
|
||||
const fstHandle* const m_symbolp = m_owner.m_symbolp;
|
||||
// String buffer long enough to hold maxBits() chars
|
||||
char* const m_strbuf = m_owner.m_strbuf;
|
||||
|
||||
public:
|
||||
// CONSTRUCTOR
|
||||
explicit VerilatedFstBuffer(VerilatedFst& owner);
|
||||
~VerilatedFstBuffer() = default;
|
||||
|
||||
//=========================================================================
|
||||
// Implementation of VerilatedTraceBuffer interface
|
||||
|
||||
// Implementations of duck-typed methods for VerilatedTraceBuffer. These are
|
||||
// called from only one place (the full* methods), so always inline them.
|
||||
VL_ATTR_ALWINLINE inline void emitBit(uint32_t code, CData newval);
|
||||
VL_ATTR_ALWINLINE inline void emitCData(uint32_t code, CData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitSData(uint32_t code, SData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitIData(uint32_t code, IData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitQData(uint32_t code, QData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitWData(uint32_t code, const WData* newvalp, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitDouble(uint32_t code, double newval);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedFstC
|
||||
/// Create a FST dump file in C standalone (no SystemC) simulations.
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class VerilatedEvalMsgQueue final {
|
|||
|
||||
std::atomic<uint64_t> m_depth; // Current depth of queue (see comments below)
|
||||
|
||||
VerilatedMutex m_mutex; // Mutex protecting queue
|
||||
mutable VerilatedMutex m_mutex; // Mutex protecting queue
|
||||
VerilatedThreadQueue m_queue VL_GUARDED_BY(m_mutex); // Message queue
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ uint16_t VlExecutionRecord::getcpu() {
|
|||
//=============================================================================
|
||||
// VlExecutionProfiler implementation
|
||||
|
||||
template <size_t N> size_t roundUptoMultipleOf(size_t value) {
|
||||
template <size_t N> static size_t roundUptoMultipleOf(size_t value) {
|
||||
static_assert((N & (N - 1)) == 0, "'N' must be a power of 2");
|
||||
size_t mask = N - 1;
|
||||
return (value + mask) & ~mask;
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ class VlExecutionProfiler final {
|
|||
|
||||
// STATE
|
||||
static VL_THREAD_LOCAL ExecutionTrace t_trace; // thread-local trace buffers
|
||||
VerilatedMutex m_mutex;
|
||||
mutable VerilatedMutex m_mutex;
|
||||
// Map from thread id to &t_trace of given thread
|
||||
std::map<uint32_t, ExecutionTrace*> m_traceps VL_GUARDED_BY(m_mutex);
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ void VlPgoProfiler<T_Entries>::write(const char* modelp, const std::string& file
|
|||
// So when we have multiple models in an executable, possibly even
|
||||
// running on different threads, each will have a different symtab so
|
||||
// each will collect is own data correctly. However when each is
|
||||
// destroid we need to get all the data, not keep overwriting and only
|
||||
// destroyed we need to get all the data, not keep overwriting and only
|
||||
// get the last model's data.
|
||||
static bool s_firstCall = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ private:
|
|||
};
|
||||
|
||||
// MEMBERS
|
||||
VerilatedMutex m_mutex;
|
||||
mutable VerilatedMutex m_mutex;
|
||||
std::condition_variable_any m_cv;
|
||||
// Only notify the condition_variable if the worker is waiting
|
||||
bool m_waiting VL_GUARDED_BY(m_mutex) = false;
|
||||
|
|
|
|||
|
|
@ -24,30 +24,49 @@
|
|||
|
||||
// clang-format off
|
||||
|
||||
// In FST mode, VL_TRACE_THREADED enables offloading, but only if we also have
|
||||
// the FST writer thread. This means with --trace-threads 1, we get the FST
|
||||
// writer thread only, and with --trace-threads 2 we get offloading as well
|
||||
#if defined(VL_TRACE_FST_WRITER_THREAD) && defined(VL_TRACE_THREADED)
|
||||
# define VL_TRACE_OFFLOAD
|
||||
#endif
|
||||
// VCD tracing can happen fully in parallel
|
||||
#if defined(VM_TRACE_VCD) && VM_TRACE_VCD && defined(VL_TRACE_THREADED)
|
||||
# define VL_TRACE_PARALLEL
|
||||
#endif
|
||||
|
||||
#if defined(VL_TRACE_PARALLEL) && defined(VL_TRACE_OFFLOAD)
|
||||
# error "Cannot have VL_TRACE_PARALLEL and VL_TRACE_OFFLOAD together"
|
||||
#endif
|
||||
|
||||
#include "verilated.h"
|
||||
#include "verilated_trace_defs.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
# include <condition_variable>
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
# include <deque>
|
||||
# include <thread>
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
class VlThreadPool;
|
||||
template <class T_Trace, class T_Buffer> class VerilatedTraceBuffer;
|
||||
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
//=============================================================================
|
||||
// Threaded tracing
|
||||
// Offloaded tracing
|
||||
|
||||
// A simple synchronized first in first out queue
|
||||
template <class T> class VerilatedThreadQueue final { // LCOV_EXCL_LINE // lcov bug
|
||||
private:
|
||||
VerilatedMutex m_mutex; // Protects m_queue
|
||||
mutable VerilatedMutex m_mutex; // Protects m_queue
|
||||
std::condition_variable_any m_cv;
|
||||
std::deque<T> m_queue VL_GUARDED_BY(m_mutex);
|
||||
|
||||
|
|
@ -88,7 +107,7 @@ public:
|
|||
|
||||
// Commands used by thread tracing. Anonymous enum in class, as we want
|
||||
// it scoped, but we also want the automatic conversion to integer types.
|
||||
class VerilatedTraceCommand final {
|
||||
class VerilatedTraceOffloadCommand final {
|
||||
public:
|
||||
// These must all fit in 4 bit at the moment, as the tracing routines
|
||||
// pack parameters in the top bits.
|
||||
|
|
@ -102,7 +121,8 @@ public:
|
|||
CHG_WDATA = 0x6,
|
||||
CHG_DOUBLE = 0x8,
|
||||
// TODO: full..
|
||||
TIME_CHANGE = 0xd,
|
||||
TIME_CHANGE = 0xc,
|
||||
TRACE_BUFFER = 0xd,
|
||||
END = 0xe, // End of buffer
|
||||
SHUTDOWN = 0xf // Shutdown worker thread, also marks end of buffer
|
||||
};
|
||||
|
|
@ -112,16 +132,22 @@ public:
|
|||
//=============================================================================
|
||||
// VerilatedTrace
|
||||
|
||||
// VerilatedTrace uses F-bounded polymorphism to access duck-typed
|
||||
// implementations in the format specific derived class, which must be passed
|
||||
// as the type parameter T_Derived
|
||||
template <class T_Derived> class VerilatedTrace VL_NOT_FINAL {
|
||||
// T_Trace is the format specific subclass of VerilatedTrace.
|
||||
// T_Buffer is the format specific subclass of VerilatedTraceBuffer.
|
||||
template <class T_Trace, class T_Buffer> class VerilatedTrace VL_NOT_FINAL {
|
||||
// Give the buffer (both base and derived) access to the private bits
|
||||
friend VerilatedTraceBuffer<T_Trace, T_Buffer>;
|
||||
friend T_Buffer;
|
||||
|
||||
public:
|
||||
using Buffer = T_Buffer;
|
||||
|
||||
//=========================================================================
|
||||
// Generic tracing internals
|
||||
|
||||
using initCb_t = void (*)(void*, T_Derived*, uint32_t); // Type of init callbacks
|
||||
using dumpCb_t = void (*)(void*, T_Derived*); // Type of all but init callbacks
|
||||
using initCb_t = void (*)(void*, T_Trace*, uint32_t); // Type of init callbacks
|
||||
using dumpCb_t = void (*)(void*, Buffer*); // Type of dump callbacks
|
||||
using cleanupCb_t = void (*)(void*, T_Trace*); // Type of cleanup callbacks
|
||||
|
||||
private:
|
||||
struct CallbackRecord {
|
||||
|
|
@ -129,9 +155,10 @@ private:
|
|||
// (the one in Ubuntu 14.04 with GCC 4.8.4 in particular) use the
|
||||
// assignment operator on inserting into collections, so they don't work
|
||||
// with const fields...
|
||||
union {
|
||||
initCb_t m_initCb; // The callback function
|
||||
dumpCb_t m_dumpCb; // The callback function
|
||||
union { // The callback
|
||||
initCb_t m_initCb;
|
||||
dumpCb_t m_dumpCb;
|
||||
cleanupCb_t m_cleanupCb;
|
||||
};
|
||||
void* m_userp; // The user pointer to pass to the callback (the symbol table)
|
||||
CallbackRecord(initCb_t cb, void* userp)
|
||||
|
|
@ -140,65 +167,103 @@ private:
|
|||
CallbackRecord(dumpCb_t cb, void* userp)
|
||||
: m_dumpCb{cb}
|
||||
, m_userp{userp} {}
|
||||
CallbackRecord(cleanupCb_t cb, void* userp)
|
||||
: m_cleanupCb{cb}
|
||||
, m_userp{userp} {}
|
||||
};
|
||||
|
||||
uint32_t* m_sigs_oldvalp; // Old value store
|
||||
EData* m_sigs_enabledp; // Bit vector of enabled codes (nullptr = all on)
|
||||
uint64_t m_timeLastDump; // Last time we did a dump
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
struct ParallelWorkerData {
|
||||
const dumpCb_t m_cb; // The callback
|
||||
void* const m_userp; // The use pointer to pass to the callback
|
||||
Buffer* const m_bufp; // The buffer pointer to pass to the callback
|
||||
std::atomic<bool> m_ready{false}; // The ready flag
|
||||
mutable VerilatedMutex m_mutex; // Mutex for suspension until ready
|
||||
std::condition_variable_any m_cv; // Condition variable for suspension
|
||||
bool m_waiting VL_GUARDED_BY(m_mutex) = false; // Whether a thread is suspended in wait()
|
||||
|
||||
void wait();
|
||||
|
||||
ParallelWorkerData(dumpCb_t cb, void* userp, Buffer* bufp)
|
||||
: m_cb{cb}
|
||||
, m_userp{userp}
|
||||
, m_bufp{bufp} {}
|
||||
};
|
||||
|
||||
// Passed a ParallelWorkerData*, second argument is ignored
|
||||
static void parallelWorkerTask(void*, bool);
|
||||
#endif
|
||||
|
||||
using ParallelCallbackMap = std::unordered_map<VlThreadPool*, std::vector<CallbackRecord>>;
|
||||
|
||||
protected:
|
||||
uint32_t* m_sigs_oldvalp = nullptr; // Previous value store
|
||||
EData* m_sigs_enabledp = nullptr; // Bit vector of enabled codes (nullptr = all on)
|
||||
private:
|
||||
uint64_t m_timeLastDump = 0; // Last time we did a dump
|
||||
std::vector<bool> m_sigs_enabledVec; // Staging for m_sigs_enabledp
|
||||
std::vector<CallbackRecord> m_initCbs; // Routines to initialize traciong
|
||||
std::vector<CallbackRecord> m_fullCbs; // Routines to perform full dump
|
||||
std::vector<CallbackRecord> m_chgCbs; // Routines to perform incremental dump
|
||||
std::vector<CallbackRecord> m_initCbs; // Routines to initialize tracing
|
||||
ParallelCallbackMap m_fullCbs; // Routines to perform full dump
|
||||
ParallelCallbackMap m_chgCbs; // Routines to perform incremental dump
|
||||
std::vector<CallbackRecord> m_cleanupCbs; // Routines to call at the end of dump
|
||||
bool m_fullDump; // Whether a full dump is required on the next call to 'dump'
|
||||
uint32_t m_nextCode; // Next code number to assign
|
||||
uint32_t m_numSignals; // Number of distinct signals
|
||||
uint32_t m_maxBits; // Number of bits in the widest signal
|
||||
std::vector<VlThreadPool*> m_threadPoolps; // All thread pools, in insertion order
|
||||
bool m_fullDump = true; // Whether a full dump is required on the next call to 'dump'
|
||||
uint32_t m_nextCode = 0; // Next code number to assign
|
||||
uint32_t m_numSignals = 0; // Number of distinct signals
|
||||
uint32_t m_maxBits = 0; // Number of bits in the widest signal
|
||||
std::vector<std::string> m_namePrefixStack{""}; // Path prefixes to add to signal names
|
||||
std::vector<std::pair<int, std::string>> m_dumpvars; // dumpvar() entries
|
||||
char m_scopeEscape;
|
||||
double m_timeRes; // Time resolution (ns/ms etc)
|
||||
double m_timeUnit; // Time units (ns/ms etc)
|
||||
char m_scopeEscape = '.';
|
||||
double m_timeRes = 1e-9; // Time resolution (ns/ms etc)
|
||||
double m_timeUnit = 1e-0; // Time units (ns/ms etc)
|
||||
|
||||
void addThreadPool(VlThreadPool* threadPoolp) VL_MT_SAFE_EXCLUDES(m_mutex);
|
||||
|
||||
void addCallbackRecord(std::vector<CallbackRecord>& cbVec, CallbackRecord& cbRec)
|
||||
VL_MT_SAFE_EXCLUDES(m_mutex);
|
||||
|
||||
// Equivalent to 'this' but is of the sub-type 'T_Derived*'. Use 'self()->'
|
||||
// Equivalent to 'this' but is of the sub-type 'T_Trace*'. Use 'self()->'
|
||||
// to access duck-typed functions to avoid a virtual function call.
|
||||
T_Derived* self() { return static_cast<T_Derived*>(this); }
|
||||
T_Trace* self() { return static_cast<T_Trace*>(this); }
|
||||
|
||||
void runParallelCallbacks(const ParallelCallbackMap& cbMap);
|
||||
|
||||
// Flush any remaining data for this file
|
||||
static void onFlush(void* selfp) VL_MT_UNSAFE_ONE;
|
||||
// Close the file on termination
|
||||
static void onExit(void* selfp) VL_MT_UNSAFE_ONE;
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
// Number of total trace buffers that have been allocated
|
||||
uint32_t m_numTraceBuffers;
|
||||
// Size of trace buffers
|
||||
size_t m_traceBufferSize;
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
// Number of total offload buffers that have been allocated
|
||||
uint32_t m_numOffloadBuffers = 0;
|
||||
// Size of offload buffers
|
||||
size_t m_offloadBufferSize = 0;
|
||||
// Buffers handed to worker for processing
|
||||
VerilatedThreadQueue<uint32_t*> m_buffersToWorker;
|
||||
VerilatedThreadQueue<uint32_t*> m_offloadBuffersToWorker;
|
||||
// Buffers returned from worker after processing
|
||||
VerilatedThreadQueue<uint32_t*> m_buffersFromWorker;
|
||||
VerilatedThreadQueue<uint32_t*> m_offloadBuffersFromWorker;
|
||||
|
||||
protected:
|
||||
// Write pointer into current buffer
|
||||
uint32_t* m_traceBufferWritep;
|
||||
// End of trace buffer
|
||||
uint32_t* m_traceBufferEndp;
|
||||
// The worker thread itself
|
||||
uint32_t* m_offloadBufferWritep = nullptr;
|
||||
// End of offload buffer
|
||||
uint32_t* m_offloadBufferEndp = nullptr;
|
||||
|
||||
private:
|
||||
// The offload worker thread itself
|
||||
std::unique_ptr<std::thread> m_workerThread;
|
||||
|
||||
// Get a new trace buffer that can be populated. May block if none available
|
||||
uint32_t* getTraceBuffer();
|
||||
// Get a new offload buffer that can be populated. May block if none available
|
||||
uint32_t* getOffloadBuffer();
|
||||
|
||||
// The function executed by the worker thread
|
||||
void workerThreadMain();
|
||||
// The function executed by the offload worker thread
|
||||
void offloadWorkerThreadMain();
|
||||
|
||||
// Wait until given buffer is placed in m_buffersFromWorker
|
||||
void waitForBuffer(const uint32_t* bufferp);
|
||||
// Wait until given offload buffer is placed in m_offloadBuffersFromWorker
|
||||
void waitForOffloadBuffer(const uint32_t* bufferp);
|
||||
|
||||
// Shut down and join worker, if it's running, otherwise do nothing
|
||||
void shutdownWorker();
|
||||
void shutdownOffloadWorker();
|
||||
#endif
|
||||
|
||||
// CONSTRUCTORS
|
||||
|
|
@ -208,7 +273,7 @@ protected:
|
|||
//=========================================================================
|
||||
// Internals available to format specific implementations
|
||||
|
||||
VerilatedMutex m_mutex; // Ensure dump() etc only called from single thread
|
||||
mutable VerilatedMutex m_mutex; // Ensure dump() etc only called from single thread
|
||||
|
||||
uint32_t nextCode() const { return m_nextCode; }
|
||||
uint32_t numSignals() const { return m_numSignals; }
|
||||
|
|
@ -246,6 +311,10 @@ protected:
|
|||
virtual bool preFullDump() = 0;
|
||||
virtual bool preChangeDump() = 0;
|
||||
|
||||
// Trace buffer management
|
||||
virtual Buffer* getTraceBuffer() = 0;
|
||||
virtual void commitTraceBuffer(Buffer*) = 0;
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// External interface to client code
|
||||
|
|
@ -266,19 +335,55 @@ public:
|
|||
// Call
|
||||
void dump(uint64_t timeui) VL_MT_SAFE_EXCLUDES(m_mutex);
|
||||
|
||||
//=========================================================================
|
||||
// Internal interface to Verilator generated code
|
||||
|
||||
//=========================================================================
|
||||
// Non-hot path internal interface to Verilator generated code
|
||||
|
||||
void addInitCb(initCb_t cb, void* userp) VL_MT_SAFE;
|
||||
void addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE;
|
||||
void addChgCb(dumpCb_t cb, void* userp) VL_MT_SAFE;
|
||||
void addCleanupCb(dumpCb_t cb, void* userp) VL_MT_SAFE;
|
||||
void addFullCb(dumpCb_t cb, void* userp, VlThreadPool* = nullptr) VL_MT_SAFE;
|
||||
void addChgCb(dumpCb_t cb, void* userp, VlThreadPool* = nullptr) VL_MT_SAFE;
|
||||
void addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE;
|
||||
|
||||
void scopeEscape(char flag) { m_scopeEscape = flag; }
|
||||
|
||||
void pushNamePrefix(const std::string&);
|
||||
void popNamePrefix(unsigned count = 1);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedTraceBuffer
|
||||
|
||||
// T_Trace is the format specific subclass of VerilatedTrace.
|
||||
// T_Buffer is the format specific subclass of VerilatedTraceBuffer.
|
||||
// The format-specific hot-path methods use duck-typing via T_Buffer for performance.
|
||||
template <class T_Trace, class T_Buffer> class VerilatedTraceBuffer VL_NOT_FINAL {
|
||||
friend T_Trace; // Give the trace file access to the private bits
|
||||
|
||||
protected:
|
||||
T_Trace& m_owner; // The VerilatedTrace subclass that owns this buffer
|
||||
|
||||
// Previous value store
|
||||
uint32_t* const m_sigs_oldvalp = m_owner.m_sigs_oldvalp;
|
||||
// Bit vector of enabled codes (nullptr = all on)
|
||||
EData* const m_sigs_enabledp = m_owner.m_sigs_enabledp;
|
||||
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
// Write pointer into current buffer
|
||||
uint32_t* m_offloadBufferWritep = m_owner.m_offloadBufferWritep;
|
||||
// End of offload buffer
|
||||
uint32_t* const m_offloadBufferEndp = m_owner.m_offloadBufferEndp;
|
||||
#endif
|
||||
|
||||
// Equivalent to 'this' but is of the sub-type 'T_Derived*'. Use 'self()->'
|
||||
// to access duck-typed functions to avoid a virtual function call.
|
||||
inline T_Buffer* self() { return static_cast<T_Buffer*>(this); }
|
||||
|
||||
explicit VerilatedTraceBuffer(T_Trace& owner);
|
||||
virtual ~VerilatedTraceBuffer() = default;
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// Hot path internal interface to Verilator generated code
|
||||
|
||||
|
|
@ -296,7 +401,7 @@ public:
|
|||
// duck-typed void emitWData(uint32_t code, const WData* newvalp, int bits) = 0;
|
||||
// duck-typed void emitDouble(uint32_t code, double newval) = 0;
|
||||
|
||||
uint32_t* oldp(uint32_t code) { return m_sigs_oldvalp + code; }
|
||||
VL_ATTR_ALWINLINE inline uint32_t* oldp(uint32_t code) { return m_sigs_oldvalp + code; }
|
||||
|
||||
// Write to previous value buffer value and emit trace entry.
|
||||
void fullBit(uint32_t* oldp, CData newval);
|
||||
|
|
@ -307,89 +412,93 @@ public:
|
|||
void fullWData(uint32_t* oldp, const WData* newvalp, int bits);
|
||||
void fullDouble(uint32_t* oldp, double newval);
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
// Threaded tracing. Just dump everything in the trace buffer
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
// Offloaded tracing. Just dump everything in the offload buffer
|
||||
inline void chgBit(uint32_t code, CData newval) {
|
||||
m_traceBufferWritep[0] = VerilatedTraceCommand::CHG_BIT_0 | newval;
|
||||
m_traceBufferWritep[1] = code;
|
||||
m_traceBufferWritep += 2;
|
||||
VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
|
||||
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::CHG_BIT_0 | newval;
|
||||
m_offloadBufferWritep[1] = code;
|
||||
m_offloadBufferWritep += 2;
|
||||
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
|
||||
}
|
||||
inline void chgCData(uint32_t code, CData newval, int bits) {
|
||||
m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_CDATA;
|
||||
m_traceBufferWritep[1] = code;
|
||||
m_traceBufferWritep[2] = newval;
|
||||
m_traceBufferWritep += 3;
|
||||
VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
|
||||
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_CDATA;
|
||||
m_offloadBufferWritep[1] = code;
|
||||
m_offloadBufferWritep[2] = newval;
|
||||
m_offloadBufferWritep += 3;
|
||||
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
|
||||
}
|
||||
inline void chgSData(uint32_t code, SData newval, int bits) {
|
||||
m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_SDATA;
|
||||
m_traceBufferWritep[1] = code;
|
||||
m_traceBufferWritep[2] = newval;
|
||||
m_traceBufferWritep += 3;
|
||||
VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
|
||||
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_SDATA;
|
||||
m_offloadBufferWritep[1] = code;
|
||||
m_offloadBufferWritep[2] = newval;
|
||||
m_offloadBufferWritep += 3;
|
||||
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
|
||||
}
|
||||
inline void chgIData(uint32_t code, IData newval, int bits) {
|
||||
m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_IDATA;
|
||||
m_traceBufferWritep[1] = code;
|
||||
m_traceBufferWritep[2] = newval;
|
||||
m_traceBufferWritep += 3;
|
||||
VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
|
||||
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_IDATA;
|
||||
m_offloadBufferWritep[1] = code;
|
||||
m_offloadBufferWritep[2] = newval;
|
||||
m_offloadBufferWritep += 3;
|
||||
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
|
||||
}
|
||||
inline void chgQData(uint32_t code, QData newval, int bits) {
|
||||
m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_QDATA;
|
||||
m_traceBufferWritep[1] = code;
|
||||
*reinterpret_cast<QData*>(m_traceBufferWritep + 2) = newval;
|
||||
m_traceBufferWritep += 4;
|
||||
VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
|
||||
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_QDATA;
|
||||
m_offloadBufferWritep[1] = code;
|
||||
*reinterpret_cast<QData*>(m_offloadBufferWritep + 2) = newval;
|
||||
m_offloadBufferWritep += 4;
|
||||
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
|
||||
}
|
||||
inline void chgWData(uint32_t code, const WData* newvalp, int bits) {
|
||||
m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_WDATA;
|
||||
m_traceBufferWritep[1] = code;
|
||||
m_traceBufferWritep += 2;
|
||||
for (int i = 0; i < (bits + 31) / 32; ++i) { *m_traceBufferWritep++ = newvalp[i]; }
|
||||
VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
|
||||
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_WDATA;
|
||||
m_offloadBufferWritep[1] = code;
|
||||
m_offloadBufferWritep += 2;
|
||||
for (int i = 0; i < (bits + 31) / 32; ++i) { *m_offloadBufferWritep++ = newvalp[i]; }
|
||||
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
|
||||
}
|
||||
inline void chgDouble(uint32_t code, double newval) {
|
||||
m_traceBufferWritep[0] = VerilatedTraceCommand::CHG_DOUBLE;
|
||||
m_traceBufferWritep[1] = code;
|
||||
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::CHG_DOUBLE;
|
||||
m_offloadBufferWritep[1] = code;
|
||||
// cppcheck-suppress invalidPointerCast
|
||||
*reinterpret_cast<double*>(m_traceBufferWritep + 2) = newval;
|
||||
m_traceBufferWritep += 4;
|
||||
VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
|
||||
*reinterpret_cast<double*>(m_offloadBufferWritep + 2) = newval;
|
||||
m_offloadBufferWritep += 4;
|
||||
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
|
||||
}
|
||||
|
||||
#define CHG(name) chg##name##Impl
|
||||
#else
|
||||
#define CHG(name) chg##name
|
||||
#define chgBit chgBitImpl
|
||||
#define chgCData chgCDataImpl
|
||||
#define chgSData chgSDataImpl
|
||||
#define chgIData chgIDataImpl
|
||||
#define chgQData chgQDataImpl
|
||||
#define chgWData chgWDataImpl
|
||||
#define chgDouble chgDoubleImpl
|
||||
#endif
|
||||
|
||||
// In non-threaded mode, these are called directly by the trace callbacks,
|
||||
// and are called chg*. In threaded mode, they are called by the worker
|
||||
// In non-offload mode, these are called directly by the trace callbacks,
|
||||
// and are called chg*. In offload mode, they are called by the worker
|
||||
// thread and are called chg*Impl
|
||||
|
||||
// Check previous dumped value of signal. If changed, then emit trace entry
|
||||
inline void CHG(Bit)(uint32_t* oldp, CData newval) {
|
||||
VL_ATTR_ALWINLINE inline void chgBit(uint32_t* oldp, CData newval) {
|
||||
const uint32_t diff = *oldp ^ newval;
|
||||
if (VL_UNLIKELY(diff)) fullBit(oldp, newval);
|
||||
}
|
||||
inline void CHG(CData)(uint32_t* oldp, CData newval, int bits) {
|
||||
VL_ATTR_ALWINLINE inline void chgCData(uint32_t* oldp, CData newval, int bits) {
|
||||
const uint32_t diff = *oldp ^ newval;
|
||||
if (VL_UNLIKELY(diff)) fullCData(oldp, newval, bits);
|
||||
}
|
||||
inline void CHG(SData)(uint32_t* oldp, SData newval, int bits) {
|
||||
VL_ATTR_ALWINLINE inline void chgSData(uint32_t* oldp, SData newval, int bits) {
|
||||
const uint32_t diff = *oldp ^ newval;
|
||||
if (VL_UNLIKELY(diff)) fullSData(oldp, newval, bits);
|
||||
}
|
||||
inline void CHG(IData)(uint32_t* oldp, IData newval, int bits) {
|
||||
VL_ATTR_ALWINLINE inline void chgIData(uint32_t* oldp, IData newval, int bits) {
|
||||
const uint32_t diff = *oldp ^ newval;
|
||||
if (VL_UNLIKELY(diff)) fullIData(oldp, newval, bits);
|
||||
}
|
||||
inline void CHG(QData)(uint32_t* oldp, QData newval, int bits) {
|
||||
VL_ATTR_ALWINLINE inline void chgQData(uint32_t* oldp, QData newval, int bits) {
|
||||
const uint64_t diff = *reinterpret_cast<QData*>(oldp) ^ newval;
|
||||
if (VL_UNLIKELY(diff)) fullQData(oldp, newval, bits);
|
||||
}
|
||||
inline void CHG(WData)(uint32_t* oldp, const WData* newvalp, int bits) {
|
||||
VL_ATTR_ALWINLINE inline void chgWData(uint32_t* oldp, const WData* newvalp, int bits) {
|
||||
for (int i = 0; i < (bits + 31) / 32; ++i) {
|
||||
if (VL_UNLIKELY(oldp[i] ^ newvalp[i])) {
|
||||
fullWData(oldp, newvalp, bits);
|
||||
|
|
@ -397,11 +506,20 @@ public:
|
|||
}
|
||||
}
|
||||
}
|
||||
inline void CHG(Double)(uint32_t* oldp, double newval) {
|
||||
VL_ATTR_ALWINLINE inline void chgDouble(uint32_t* oldp, double newval) {
|
||||
// cppcheck-suppress invalidPointerCast
|
||||
if (VL_UNLIKELY(*reinterpret_cast<double*>(oldp) != newval)) fullDouble(oldp, newval);
|
||||
}
|
||||
|
||||
#undef CHG
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
#undef chgBit
|
||||
#undef chgCData
|
||||
#undef chgSData
|
||||
#undef chgIData
|
||||
#undef chgQData
|
||||
#undef chgWData
|
||||
#undef chgDouble
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // guard
|
||||
|
|
|
|||
|
|
@ -10,32 +10,32 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//=============================================================================
|
||||
///
|
||||
/// \file
|
||||
/// \brief Verilated common-format tracing implementation code
|
||||
///
|
||||
/// This file must be compiled and linked against all Verilated objects
|
||||
/// that use --trace.
|
||||
///
|
||||
/// Use "verilator --trace" to add this to the Makefile for the linker.
|
||||
///
|
||||
//
|
||||
// Verilated tracing implementation code template common to all formats.
|
||||
// This file is included by the format specific implementations and
|
||||
// should not be used otherwise.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef VL_CPPCHECK
|
||||
#ifndef VL_DERIVED_T
|
||||
#if !defined(VL_SUB_T) || !defined(VL_BUF_T)
|
||||
# error "This file should be included in trace format implementations"
|
||||
#endif
|
||||
|
||||
#include "verilated_intrinsics.h"
|
||||
#include "verilated_trace.h"
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
# include "verilated_threads.h"
|
||||
# include <list>
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
# include <iostream>
|
||||
# define VL_TRACE_THREAD_DEBUG(msg) std::cout << "TRACE THREAD: " << msg << std::endl
|
||||
# define VL_TRACE_OFFLOAD_DEBUG(msg) std::cout << "TRACE OFFLOAD THREAD: " << msg << std::endl
|
||||
#else
|
||||
# define VL_TRACE_THREAD_DEBUG(msg)
|
||||
# define VL_TRACE_OFFLOAD_DEBUG(msg)
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
|
@ -78,37 +78,37 @@ static std::string doubleToTimescale(double value) {
|
|||
return valuestr; // Gets converted to string, so no ref to stack
|
||||
}
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
//=========================================================================
|
||||
// Buffer management
|
||||
|
||||
template <> uint32_t* VerilatedTrace<VL_DERIVED_T>::getTraceBuffer() {
|
||||
template <> uint32_t* VerilatedTrace<VL_SUB_T, VL_BUF_T>::getOffloadBuffer() {
|
||||
uint32_t* bufferp;
|
||||
// Some jitter is expected, so some number of alternative trace buffers are
|
||||
// Some jitter is expected, so some number of alternative offlaod buffers are
|
||||
// required, but don't allocate more than 8 buffers.
|
||||
if (m_numTraceBuffers < 8) {
|
||||
if (m_numOffloadBuffers < 8) {
|
||||
// Allocate a new buffer if none is available
|
||||
if (!m_buffersFromWorker.tryGet(bufferp)) {
|
||||
++m_numTraceBuffers;
|
||||
if (!m_offloadBuffersFromWorker.tryGet(bufferp)) {
|
||||
++m_numOffloadBuffers;
|
||||
// Note: over allocate a bit so pointer comparison is well defined
|
||||
// if we overflow only by a small amount
|
||||
bufferp = new uint32_t[m_traceBufferSize + 16];
|
||||
bufferp = new uint32_t[m_offloadBufferSize + 16];
|
||||
}
|
||||
} else {
|
||||
// Block until a buffer becomes available
|
||||
bufferp = m_buffersFromWorker.get();
|
||||
bufferp = m_offloadBuffersFromWorker.get();
|
||||
}
|
||||
return bufferp;
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::waitForBuffer(const uint32_t* buffp) {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::waitForOffloadBuffer(const uint32_t* buffp) {
|
||||
// Slow path code only called on flush/shutdown, so use a simple algorithm.
|
||||
// Collect buffers from worker and stash them until we get the one we want.
|
||||
std::deque<uint32_t*> stash;
|
||||
do { stash.push_back(m_buffersFromWorker.get()); } while (stash.back() != buffp);
|
||||
do { stash.push_back(m_offloadBuffersFromWorker.get()); } while (stash.back() != buffp);
|
||||
// Now put them back in the queue, in the original order.
|
||||
while (!stash.empty()) {
|
||||
m_buffersFromWorker.put_front(stash.back());
|
||||
m_offloadBuffersFromWorker.put_front(stash.back());
|
||||
stash.pop_back();
|
||||
}
|
||||
}
|
||||
|
|
@ -116,17 +116,19 @@ template <> void VerilatedTrace<VL_DERIVED_T>::waitForBuffer(const uint32_t* buf
|
|||
//=========================================================================
|
||||
// Worker thread
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::workerThreadMain() {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::offloadWorkerThreadMain() {
|
||||
bool shutdown = false;
|
||||
|
||||
do {
|
||||
uint32_t* const bufferp = m_buffersToWorker.get();
|
||||
uint32_t* const bufferp = m_offloadBuffersToWorker.get();
|
||||
|
||||
VL_TRACE_THREAD_DEBUG("");
|
||||
VL_TRACE_THREAD_DEBUG("Got buffer: " << bufferp);
|
||||
VL_TRACE_OFFLOAD_DEBUG("");
|
||||
VL_TRACE_OFFLOAD_DEBUG("Got buffer: " << bufferp);
|
||||
|
||||
const uint32_t* readp = bufferp;
|
||||
|
||||
std::unique_ptr<VL_BUF_T> traceBufp; // We own the passed tracebuffer
|
||||
|
||||
while (true) {
|
||||
const uint32_t cmd = readp[0];
|
||||
const uint32_t top = cmd >> 4;
|
||||
|
|
@ -139,71 +141,79 @@ template <> void VerilatedTrace<VL_DERIVED_T>::workerThreadMain() {
|
|||
switch (cmd & 0xF) {
|
||||
//===
|
||||
// CHG_* commands
|
||||
case VerilatedTraceCommand::CHG_BIT_0:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_BIT_0 " << top);
|
||||
chgBitImpl(oldp, 0);
|
||||
case VerilatedTraceOffloadCommand::CHG_BIT_0:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_BIT_0 " << top);
|
||||
traceBufp->chgBitImpl(oldp, 0);
|
||||
continue;
|
||||
case VerilatedTraceCommand::CHG_BIT_1:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_BIT_1 " << top);
|
||||
chgBitImpl(oldp, 1);
|
||||
case VerilatedTraceOffloadCommand::CHG_BIT_1:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_BIT_1 " << top);
|
||||
traceBufp->chgBitImpl(oldp, 1);
|
||||
continue;
|
||||
case VerilatedTraceCommand::CHG_CDATA:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_CDATA " << top);
|
||||
case VerilatedTraceOffloadCommand::CHG_CDATA:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_CDATA " << top);
|
||||
// Bits stored in bottom byte of command
|
||||
chgCDataImpl(oldp, *readp, top);
|
||||
traceBufp->chgCDataImpl(oldp, *readp, top);
|
||||
readp += 1;
|
||||
continue;
|
||||
case VerilatedTraceCommand::CHG_SDATA:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_SDATA " << top);
|
||||
case VerilatedTraceOffloadCommand::CHG_SDATA:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_SDATA " << top);
|
||||
// Bits stored in bottom byte of command
|
||||
chgSDataImpl(oldp, *readp, top);
|
||||
traceBufp->chgSDataImpl(oldp, *readp, top);
|
||||
readp += 1;
|
||||
continue;
|
||||
case VerilatedTraceCommand::CHG_IDATA:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_IDATA " << top);
|
||||
case VerilatedTraceOffloadCommand::CHG_IDATA:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_IDATA " << top);
|
||||
// Bits stored in bottom byte of command
|
||||
chgIDataImpl(oldp, *readp, top);
|
||||
traceBufp->chgIDataImpl(oldp, *readp, top);
|
||||
readp += 1;
|
||||
continue;
|
||||
case VerilatedTraceCommand::CHG_QDATA:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_QDATA " << top);
|
||||
case VerilatedTraceOffloadCommand::CHG_QDATA:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_QDATA " << top);
|
||||
// Bits stored in bottom byte of command
|
||||
chgQDataImpl(oldp, *reinterpret_cast<const QData*>(readp), top);
|
||||
traceBufp->chgQDataImpl(oldp, *reinterpret_cast<const QData*>(readp), top);
|
||||
readp += 2;
|
||||
continue;
|
||||
case VerilatedTraceCommand::CHG_WDATA:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_WDATA " << top);
|
||||
chgWDataImpl(oldp, readp, top);
|
||||
case VerilatedTraceOffloadCommand::CHG_WDATA:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_WDATA " << top);
|
||||
traceBufp->chgWDataImpl(oldp, readp, top);
|
||||
readp += VL_WORDS_I(top);
|
||||
continue;
|
||||
case VerilatedTraceCommand::CHG_DOUBLE:
|
||||
VL_TRACE_THREAD_DEBUG("Command CHG_DOUBLE " << top);
|
||||
chgDoubleImpl(oldp, *reinterpret_cast<const double*>(readp));
|
||||
case VerilatedTraceOffloadCommand::CHG_DOUBLE:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command CHG_DOUBLE " << top);
|
||||
traceBufp->chgDoubleImpl(oldp, *reinterpret_cast<const double*>(readp));
|
||||
readp += 2;
|
||||
continue;
|
||||
|
||||
//===
|
||||
// Rare commands
|
||||
case VerilatedTraceCommand::TIME_CHANGE:
|
||||
VL_TRACE_THREAD_DEBUG("Command TIME_CHANGE " << top);
|
||||
case VerilatedTraceOffloadCommand::TIME_CHANGE:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command TIME_CHANGE " << top);
|
||||
readp -= 1; // No code in this command, undo increment
|
||||
emitTimeChange(*reinterpret_cast<const uint64_t*>(readp));
|
||||
readp += 2;
|
||||
continue;
|
||||
|
||||
case VerilatedTraceOffloadCommand::TRACE_BUFFER:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command TRACE_BUFFER " << top);
|
||||
readp -= 1; // No code in this command, undo increment
|
||||
traceBufp.reset(*reinterpret_cast<VL_BUF_T* const*>(readp));
|
||||
readp += 2;
|
||||
continue;
|
||||
|
||||
//===
|
||||
// Commands ending this buffer
|
||||
case VerilatedTraceCommand::END: VL_TRACE_THREAD_DEBUG("Command END"); break;
|
||||
case VerilatedTraceCommand::SHUTDOWN:
|
||||
VL_TRACE_THREAD_DEBUG("Command SHUTDOWN");
|
||||
case VerilatedTraceOffloadCommand::END: //
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command END");
|
||||
break;
|
||||
case VerilatedTraceOffloadCommand::SHUTDOWN:
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command SHUTDOWN");
|
||||
shutdown = true;
|
||||
break;
|
||||
|
||||
//===
|
||||
// Unknown command
|
||||
default: { // LCOV_EXCL_START
|
||||
VL_TRACE_THREAD_DEBUG("Command UNKNOWN");
|
||||
VL_PRINTF_MT("Trace command: 0x%08x\n", cmd);
|
||||
VL_TRACE_OFFLOAD_DEBUG("Command UNKNOWN " << cmd);
|
||||
VL_FATAL_MT(__FILE__, __LINE__, "", "Unknown trace command");
|
||||
break;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
|
@ -214,23 +224,23 @@ template <> void VerilatedTrace<VL_DERIVED_T>::workerThreadMain() {
|
|||
break;
|
||||
}
|
||||
|
||||
VL_TRACE_THREAD_DEBUG("Returning buffer");
|
||||
VL_TRACE_OFFLOAD_DEBUG("Returning buffer");
|
||||
|
||||
// Return buffer
|
||||
m_buffersFromWorker.put(bufferp);
|
||||
m_offloadBuffersFromWorker.put(bufferp);
|
||||
} while (VL_LIKELY(!shutdown));
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::shutdownWorker() {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::shutdownOffloadWorker() {
|
||||
// If the worker thread is not running, done..
|
||||
if (!m_workerThread) return;
|
||||
|
||||
// Hand an buffer with a shutdown command to the worker thread
|
||||
uint32_t* const bufferp = getTraceBuffer();
|
||||
bufferp[0] = VerilatedTraceCommand::SHUTDOWN;
|
||||
m_buffersToWorker.put(bufferp);
|
||||
uint32_t* const bufferp = getOffloadBuffer();
|
||||
bufferp[0] = VerilatedTraceOffloadCommand::SHUTDOWN;
|
||||
m_offloadBuffersToWorker.put(bufferp);
|
||||
// Wait for it to return
|
||||
waitForBuffer(bufferp);
|
||||
waitForOffloadBuffer(bufferp);
|
||||
// Join the thread and delete it
|
||||
m_workerThread->join();
|
||||
m_workerThread.reset(nullptr);
|
||||
|
|
@ -241,72 +251,55 @@ template <> void VerilatedTrace<VL_DERIVED_T>::shutdownWorker() {
|
|||
//=============================================================================
|
||||
// Life cycle
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::closeBase() {
|
||||
#ifdef VL_TRACE_THREADED
|
||||
shutdownWorker();
|
||||
while (m_numTraceBuffers) {
|
||||
delete[] m_buffersFromWorker.get();
|
||||
--m_numTraceBuffers;
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::closeBase() {
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
shutdownOffloadWorker();
|
||||
while (m_numOffloadBuffers) {
|
||||
delete[] m_offloadBuffersFromWorker.get();
|
||||
--m_numOffloadBuffers;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::flushBase() {
|
||||
#ifdef VL_TRACE_THREADED
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::flushBase() {
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
// Hand an empty buffer to the worker thread
|
||||
uint32_t* const bufferp = getTraceBuffer();
|
||||
*bufferp = VerilatedTraceCommand::END;
|
||||
m_buffersToWorker.put(bufferp);
|
||||
uint32_t* const bufferp = getOffloadBuffer();
|
||||
*bufferp = VerilatedTraceOffloadCommand::END;
|
||||
m_offloadBuffersToWorker.put(bufferp);
|
||||
// Wait for it to be returned. As the processing is in-order,
|
||||
// this ensures all previous buffers have been processed.
|
||||
waitForBuffer(bufferp);
|
||||
waitForOffloadBuffer(bufferp);
|
||||
#endif
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Callbacks to run on global events
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::onFlush(void* selfp) {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::onFlush(void* selfp) {
|
||||
// This calls 'flush' on the derived class (which must then get any mutex)
|
||||
reinterpret_cast<VL_DERIVED_T*>(selfp)->flush();
|
||||
reinterpret_cast<VL_SUB_T*>(selfp)->flush();
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::onExit(void* selfp) {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::onExit(void* selfp) {
|
||||
// This calls 'close' on the derived class (which must then get any mutex)
|
||||
reinterpret_cast<VL_DERIVED_T*>(selfp)->close();
|
||||
reinterpret_cast<VL_SUB_T*>(selfp)->close();
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedTrace
|
||||
|
||||
template <>
|
||||
VerilatedTrace<VL_DERIVED_T>::VerilatedTrace()
|
||||
: m_sigs_oldvalp{nullptr}
|
||||
, m_sigs_enabledp{nullptr}
|
||||
, m_timeLastDump{0}
|
||||
, m_fullDump{true}
|
||||
, m_nextCode{0}
|
||||
, m_numSignals{0}
|
||||
, m_maxBits{0}
|
||||
, m_scopeEscape{'.'}
|
||||
, m_timeRes{1e-9}
|
||||
, m_timeUnit {
|
||||
1e-9
|
||||
}
|
||||
#ifdef VL_TRACE_THREADED
|
||||
, m_numTraceBuffers { 0 }
|
||||
#endif
|
||||
{
|
||||
template <> VerilatedTrace<VL_SUB_T, VL_BUF_T>::VerilatedTrace() {
|
||||
set_time_unit(Verilated::threadContextp()->timeunitString());
|
||||
set_time_resolution(Verilated::threadContextp()->timeprecisionString());
|
||||
}
|
||||
|
||||
template <> VerilatedTrace<VL_DERIVED_T>::~VerilatedTrace() {
|
||||
template <> VerilatedTrace<VL_SUB_T, VL_BUF_T>::~VerilatedTrace() {
|
||||
if (m_sigs_oldvalp) VL_DO_CLEAR(delete[] m_sigs_oldvalp, m_sigs_oldvalp = nullptr);
|
||||
if (m_sigs_enabledp) VL_DO_CLEAR(delete[] m_sigs_enabledp, m_sigs_enabledp = nullptr);
|
||||
Verilated::removeFlushCb(VerilatedTrace<VL_DERIVED_T>::onFlush, this);
|
||||
Verilated::removeExitCb(VerilatedTrace<VL_DERIVED_T>::onExit, this);
|
||||
#ifdef VL_TRACE_THREADED
|
||||
Verilated::removeFlushCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onFlush, this);
|
||||
Verilated::removeExitCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onExit, this);
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
closeBase();
|
||||
#endif
|
||||
}
|
||||
|
|
@ -314,7 +307,7 @@ template <> VerilatedTrace<VL_DERIVED_T>::~VerilatedTrace() {
|
|||
//=========================================================================
|
||||
// Internals available to format specific implementations
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::traceInit() VL_MT_UNSAFE {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::traceInit() VL_MT_UNSAFE {
|
||||
// Note: It is possible to re-open a trace file (VCD in particular),
|
||||
// so we must reset the next code here, but it must have the same number
|
||||
// of codes on re-open
|
||||
|
|
@ -359,25 +352,26 @@ template <> void VerilatedTrace<VL_DERIVED_T>::traceInit() VL_MT_UNSAFE {
|
|||
}
|
||||
|
||||
// Set callback so flush/abort will flush this file
|
||||
Verilated::addFlushCb(VerilatedTrace<VL_DERIVED_T>::onFlush, this);
|
||||
Verilated::addExitCb(VerilatedTrace<VL_DERIVED_T>::onExit, this);
|
||||
Verilated::addFlushCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onFlush, this);
|
||||
Verilated::addExitCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onExit, this);
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
// Compute trace buffer size. we need to be able to store a new value for
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
// Compute offload buffer size. we need to be able to store a new value for
|
||||
// each signal, which is 'nextCode()' entries after the init callbacks
|
||||
// above have been run, plus up to 2 more words of metadata per signal,
|
||||
// plus fixed overhead of 1 for a termination flag and 3 for a time stamp
|
||||
// update.
|
||||
m_traceBufferSize = nextCode() + numSignals() * 2 + 4;
|
||||
m_offloadBufferSize = nextCode() + numSignals() * 2 + 4;
|
||||
|
||||
// Start the worker thread
|
||||
m_workerThread.reset(new std::thread{&VerilatedTrace<VL_DERIVED_T>::workerThreadMain, this});
|
||||
m_workerThread.reset(
|
||||
new std::thread{&VerilatedTrace<VL_SUB_T, VL_BUF_T>::offloadWorkerThreadMain, this});
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
bool VerilatedTrace<VL_DERIVED_T>::declCode(uint32_t code, const char* namep, uint32_t bits,
|
||||
bool tri) {
|
||||
bool VerilatedTrace<VL_SUB_T, VL_BUF_T>::declCode(uint32_t code, const char* namep, uint32_t bits,
|
||||
bool tri) {
|
||||
if (VL_UNCOVERABLE(!code)) {
|
||||
VL_FATAL_MT(__FILE__, __LINE__, "", "Internal: internal trace problem, code 0 is illegal");
|
||||
}
|
||||
|
|
@ -421,28 +415,30 @@ bool VerilatedTrace<VL_DERIVED_T>::declCode(uint32_t code, const char* namep, ui
|
|||
//=========================================================================
|
||||
// Internals available to format specific implementations
|
||||
|
||||
template <> std::string VerilatedTrace<VL_DERIVED_T>::timeResStr() const {
|
||||
template <> std::string VerilatedTrace<VL_SUB_T, VL_BUF_T>::timeResStr() const {
|
||||
return doubleToTimescale(m_timeRes);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// External interface to client code
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::set_time_unit(const char* unitp) VL_MT_SAFE {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::set_time_unit(const char* unitp) VL_MT_SAFE {
|
||||
m_timeUnit = timescaleToDouble(unitp);
|
||||
}
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::set_time_unit(const std::string& unit) VL_MT_SAFE {
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::set_time_unit(const std::string& unit) VL_MT_SAFE {
|
||||
set_time_unit(unit.c_str());
|
||||
}
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::set_time_resolution(const char* unitp) VL_MT_SAFE {
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::set_time_resolution(const char* unitp) VL_MT_SAFE {
|
||||
m_timeRes = timescaleToDouble(unitp);
|
||||
}
|
||||
template <>
|
||||
void VerilatedTrace<VL_DERIVED_T>::set_time_resolution(const std::string& unit) VL_MT_SAFE {
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::set_time_resolution(const std::string& unit) VL_MT_SAFE {
|
||||
set_time_resolution(unit.c_str());
|
||||
}
|
||||
template <>
|
||||
void VerilatedTrace<VL_DERIVED_T>::dumpvars(int level, const std::string& hier) VL_MT_SAFE {
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::dumpvars(int level, const std::string& hier) VL_MT_SAFE {
|
||||
if (level == 0) {
|
||||
m_dumpvars.clear(); // empty = everything on
|
||||
} else {
|
||||
|
|
@ -455,7 +451,87 @@ void VerilatedTrace<VL_DERIVED_T>::dumpvars(int level, const std::string& hier)
|
|||
}
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::dump(uint64_t timeui) VL_MT_SAFE_EXCLUDES(m_mutex) {
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
template <> //
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::parallelWorkerTask(void* datap, bool) {
|
||||
ParallelWorkerData* const wdp = reinterpret_cast<ParallelWorkerData*>(datap);
|
||||
// Run the task
|
||||
wdp->m_cb(wdp->m_userp, wdp->m_bufp);
|
||||
// Mark buffer as ready
|
||||
const VerilatedLockGuard lock{wdp->m_mutex};
|
||||
wdp->m_ready.store(true);
|
||||
if (wdp->m_waiting) wdp->m_cv.notify_one();
|
||||
}
|
||||
|
||||
template <> VL_ATTR_NOINLINE void VerilatedTrace<VL_SUB_T, VL_BUF_T>::ParallelWorkerData::wait() {
|
||||
// Spin for a while, waiting for the buffer to become ready
|
||||
for (int i = 0; i < VL_LOCK_SPINS; ++i) {
|
||||
if (VL_LIKELY(m_ready.load(std::memory_order_relaxed))) return;
|
||||
VL_CPU_RELAX();
|
||||
}
|
||||
// We have been spinning for a while, so yield the thread
|
||||
VerilatedLockGuard lock{m_mutex};
|
||||
m_waiting = true;
|
||||
m_cv.wait(lock, [this] { return m_ready.load(std::memory_order_relaxed); });
|
||||
m_waiting = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::runParallelCallbacks(const ParallelCallbackMap& cbMap) {
|
||||
for (VlThreadPool* threadPoolp : m_threadPoolps) {
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
// If tracing in parallel, dispatch to the thread pool (if exists)
|
||||
if (threadPoolp && threadPoolp->numThreads()) {
|
||||
// List of work items for thread (std::list, as ParallelWorkerData is not movable)
|
||||
std::list<ParallelWorkerData> workerData;
|
||||
// We use the whole pool + the main thread
|
||||
const unsigned threads = threadPoolp->numThreads() + 1;
|
||||
// Main thread executes all jobs with index % threads == 0
|
||||
std::vector<ParallelWorkerData*> mainThreadWorkerData;
|
||||
// The tracing callbacks to execute on this thread-pool
|
||||
const auto& cbVec = cbMap.at(threadPoolp);
|
||||
// Enuque all the jobs
|
||||
for (unsigned i = 0; i < cbVec.size(); ++i) {
|
||||
const CallbackRecord& cbr = cbVec[i];
|
||||
// Always get the trace buffer on the main thread
|
||||
Buffer* const bufp = getTraceBuffer();
|
||||
// Create new work item
|
||||
workerData.emplace_back(cbr.m_dumpCb, cbr.m_userp, bufp);
|
||||
// Grab the new work item
|
||||
ParallelWorkerData* const itemp = &workerData.back();
|
||||
// Enqueue task to thread pool, or main thread
|
||||
if (unsigned rem = i % threads) {
|
||||
threadPoolp->workerp(rem - 1)->addTask(parallelWorkerTask, itemp, false);
|
||||
} else {
|
||||
mainThreadWorkerData.push_back(itemp);
|
||||
}
|
||||
}
|
||||
// Execute main thead jobs
|
||||
for (ParallelWorkerData* const itemp : mainThreadWorkerData) {
|
||||
parallelWorkerTask(itemp, false);
|
||||
}
|
||||
// Commit all trace buffers in order
|
||||
for (ParallelWorkerData& item : workerData) {
|
||||
// Wait until ready
|
||||
item.wait();
|
||||
// Commit the buffer
|
||||
commitTraceBuffer(item.m_bufp);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
// Fall back on sequential execution
|
||||
for (const CallbackRecord& cbr : cbMap.at(threadPoolp)) {
|
||||
Buffer* const traceBufferp = getTraceBuffer();
|
||||
cbr.m_dumpCb(cbr.m_userp, traceBufferp);
|
||||
commitTraceBuffer(traceBufferp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::dump(uint64_t timeui) VL_MT_SAFE_EXCLUDES(m_mutex) {
|
||||
// Not really VL_MT_SAFE but more VL_MT_UNSAFE_ONE.
|
||||
// This does get the mutex, but if multiple threads are trying to dump
|
||||
// chances are the data being dumped will have other problems
|
||||
|
|
@ -477,19 +553,19 @@ template <> void VerilatedTrace<VL_DERIVED_T>::dump(uint64_t timeui) VL_MT_SAFE_
|
|||
if (!preChangeDump()) return;
|
||||
}
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
// Currently only incremental dumps run on the worker thread
|
||||
uint32_t* bufferp = nullptr;
|
||||
if (VL_LIKELY(!m_fullDump)) {
|
||||
// Get the trace buffer we are about to fill
|
||||
bufferp = getTraceBuffer();
|
||||
m_traceBufferWritep = bufferp;
|
||||
m_traceBufferEndp = bufferp + m_traceBufferSize;
|
||||
// Get the offload buffer we are about to fill
|
||||
bufferp = getOffloadBuffer();
|
||||
m_offloadBufferWritep = bufferp;
|
||||
m_offloadBufferEndp = bufferp + m_offloadBufferSize;
|
||||
|
||||
// Tell worker to update time point
|
||||
m_traceBufferWritep[0] = VerilatedTraceCommand::TIME_CHANGE;
|
||||
*reinterpret_cast<uint64_t*>(m_traceBufferWritep + 1) = timeui;
|
||||
m_traceBufferWritep += 3;
|
||||
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::TIME_CHANGE;
|
||||
*reinterpret_cast<uint64_t*>(m_offloadBufferWritep + 1) = timeui;
|
||||
m_offloadBufferWritep += 3;
|
||||
} else {
|
||||
// Update time point
|
||||
flushBase();
|
||||
|
|
@ -503,32 +579,26 @@ template <> void VerilatedTrace<VL_DERIVED_T>::dump(uint64_t timeui) VL_MT_SAFE_
|
|||
// Run the callbacks
|
||||
if (VL_UNLIKELY(m_fullDump)) {
|
||||
m_fullDump = false; // No more need for next dump to be full
|
||||
for (uint32_t i = 0; i < m_fullCbs.size(); ++i) {
|
||||
const CallbackRecord& cbr = m_fullCbs[i];
|
||||
cbr.m_dumpCb(cbr.m_userp, self());
|
||||
}
|
||||
runParallelCallbacks(m_fullCbs);
|
||||
} else {
|
||||
for (uint32_t i = 0; i < m_chgCbs.size(); ++i) {
|
||||
const CallbackRecord& cbr = m_chgCbs[i];
|
||||
cbr.m_dumpCb(cbr.m_userp, self());
|
||||
}
|
||||
runParallelCallbacks(m_chgCbs);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < m_cleanupCbs.size(); ++i) {
|
||||
const CallbackRecord& cbr = m_cleanupCbs[i];
|
||||
cbr.m_dumpCb(cbr.m_userp, self());
|
||||
cbr.m_cleanupCb(cbr.m_userp, self());
|
||||
}
|
||||
|
||||
#ifdef VL_TRACE_THREADED
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
if (VL_LIKELY(bufferp)) {
|
||||
// Mark end of the trace buffer we just filled
|
||||
*m_traceBufferWritep++ = VerilatedTraceCommand::END;
|
||||
// Mark end of the offload buffer we just filled
|
||||
*m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::END;
|
||||
|
||||
// Assert no buffer overflow
|
||||
assert(m_traceBufferWritep - bufferp <= m_traceBufferSize);
|
||||
assert(m_offloadBufferWritep - bufferp <= m_offloadBufferSize);
|
||||
|
||||
// Pass it to the worker thread
|
||||
m_buffersToWorker.put(bufferp);
|
||||
m_offloadBuffersToWorker.put(bufferp);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -537,8 +607,18 @@ template <> void VerilatedTrace<VL_DERIVED_T>::dump(uint64_t timeui) VL_MT_SAFE_
|
|||
// Non-hot path internal interface to Verilator generated code
|
||||
|
||||
template <>
|
||||
void VerilatedTrace<VL_DERIVED_T>::addCallbackRecord(std::vector<CallbackRecord>& cbVec,
|
||||
CallbackRecord& cbRec)
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addThreadPool(VlThreadPool* threadPoolp)
|
||||
VL_MT_SAFE_EXCLUDES(m_mutex) {
|
||||
const VerilatedLockGuard lock{m_mutex};
|
||||
for (VlThreadPool* const poolp : m_threadPoolps) {
|
||||
if (poolp == threadPoolp) return;
|
||||
}
|
||||
m_threadPoolps.push_back(threadPoolp);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addCallbackRecord(std::vector<CallbackRecord>& cbVec,
|
||||
CallbackRecord& cbRec)
|
||||
VL_MT_SAFE_EXCLUDES(m_mutex) {
|
||||
const VerilatedLockGuard lock{m_mutex};
|
||||
if (VL_UNCOVERABLE(timeLastDump() != 0)) { // LCOV_EXCL_START
|
||||
|
|
@ -549,91 +629,40 @@ void VerilatedTrace<VL_DERIVED_T>::addCallbackRecord(std::vector<CallbackRecord>
|
|||
cbVec.push_back(cbRec);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::addInitCb(initCb_t cb, void* userp) VL_MT_SAFE {
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addInitCb(initCb_t cb, void* userp) VL_MT_SAFE {
|
||||
CallbackRecord cbr{cb, userp};
|
||||
addCallbackRecord(m_initCbs, cbr);
|
||||
}
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE {
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addFullCb(dumpCb_t cb, void* userp,
|
||||
VlThreadPool* threadPoolp) VL_MT_SAFE {
|
||||
CallbackRecord cbr{cb, userp};
|
||||
addCallbackRecord(m_fullCbs, cbr);
|
||||
addThreadPool(threadPoolp);
|
||||
addCallbackRecord(m_fullCbs[threadPoolp], cbr);
|
||||
}
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::addChgCb(dumpCb_t cb, void* userp) VL_MT_SAFE {
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addChgCb(dumpCb_t cb, void* userp,
|
||||
VlThreadPool* threadPoolp) VL_MT_SAFE {
|
||||
CallbackRecord cbr{cb, userp};
|
||||
addCallbackRecord(m_chgCbs, cbr);
|
||||
addThreadPool(threadPoolp);
|
||||
addCallbackRecord(m_chgCbs[threadPoolp], cbr);
|
||||
}
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::addCleanupCb(dumpCb_t cb, void* userp) VL_MT_SAFE {
|
||||
template <>
|
||||
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE {
|
||||
CallbackRecord cbr{cb, userp};
|
||||
addCallbackRecord(m_cleanupCbs, cbr);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::pushNamePrefix(const std::string& prefix) {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::pushNamePrefix(const std::string& prefix) {
|
||||
m_namePrefixStack.push_back(m_namePrefixStack.back() + prefix);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::popNamePrefix(unsigned count) {
|
||||
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::popNamePrefix(unsigned count) {
|
||||
while (count--) m_namePrefixStack.pop_back();
|
||||
assert(!m_namePrefixStack.empty());
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Hot path internal interface to Verilator generated code
|
||||
|
||||
// These functions must write the new value back into the old value store,
|
||||
// and subsequently call the format specific emit* implementations. Note
|
||||
// that this file must be included in the format specific implementation, so
|
||||
// the emit* functions can be inlined for performance.
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::fullBit(uint32_t* oldp, CData newval) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitBit(code, newval);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::fullCData(uint32_t* oldp, CData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitCData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::fullSData(uint32_t* oldp, SData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitSData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::fullIData(uint32_t* oldp, IData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitIData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::fullQData(uint32_t* oldp, QData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*reinterpret_cast<QData*>(oldp) = newval;
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitQData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTrace<VL_DERIVED_T>::fullWData(uint32_t* oldp, const WData* newvalp, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
for (int i = 0; i < VL_WORDS_I(bits); ++i) oldp[i] = newvalp[i];
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitWData(code, newvalp, bits);
|
||||
}
|
||||
|
||||
template <> void VerilatedTrace<VL_DERIVED_T>::fullDouble(uint32_t* oldp, double newval) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*reinterpret_cast<double*>(oldp) = newval;
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
// cppcheck-suppress invalidPointerCast
|
||||
self()->emitDouble(code, newval);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Primitives converting binary values to strings...
|
||||
|
||||
|
|
@ -724,41 +753,86 @@ static inline void cvtQDataToStr(char* dstp, QData value) {
|
|||
|
||||
#define cvtEDataToStr cvtIDataToStr
|
||||
|
||||
//=============================================================================
|
||||
//=========================================================================
|
||||
// VerilatedTraceBuffer
|
||||
|
||||
#ifdef VERILATED_VCD_TEST
|
||||
|
||||
void verilated_trace_imp_selftest() {
|
||||
#define SELF_CHECK(got, exp) \
|
||||
do { \
|
||||
if ((got) != (exp)) VL_FATAL_MT(__FILE__, __LINE__, "", "%Error: selftest"); \
|
||||
} while (0)
|
||||
|
||||
#define SELF_CHECK_TS(scale) \
|
||||
SELF_CHECK(doubleToTimescale(timescaleToDouble(scale)), std::string{scale});
|
||||
SELF_CHECK_TS("100s");
|
||||
SELF_CHECK_TS("10s");
|
||||
SELF_CHECK_TS("1s");
|
||||
SELF_CHECK_TS("100ms");
|
||||
SELF_CHECK_TS("10ms");
|
||||
SELF_CHECK_TS("1ms");
|
||||
SELF_CHECK_TS("100us");
|
||||
SELF_CHECK_TS("10us");
|
||||
SELF_CHECK_TS("1us");
|
||||
SELF_CHECK_TS("100ns");
|
||||
SELF_CHECK_TS("10ns");
|
||||
SELF_CHECK_TS("1ns");
|
||||
SELF_CHECK_TS("100ps");
|
||||
SELF_CHECK_TS("10ps");
|
||||
SELF_CHECK_TS("1ps");
|
||||
SELF_CHECK_TS("100fs");
|
||||
SELF_CHECK_TS("10fs");
|
||||
SELF_CHECK_TS("1fs");
|
||||
SELF_CHECK_TS("100as");
|
||||
SELF_CHECK_TS("10as");
|
||||
SELF_CHECK_TS("1as");
|
||||
template <> //
|
||||
VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::VerilatedTraceBuffer(VL_SUB_T& owner)
|
||||
: m_owner{owner} {
|
||||
#ifdef VL_TRACE_OFFLOAD
|
||||
if (m_offloadBufferWritep) {
|
||||
using This = VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>*;
|
||||
// Tack on the buffer address
|
||||
static_assert(2 * sizeof(uint32_t) >= sizeof(This),
|
||||
"This should be enough on all plafrorms");
|
||||
*m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::TRACE_BUFFER;
|
||||
*reinterpret_cast<This*>(m_offloadBufferWritep) = this;
|
||||
m_offloadBufferWritep += 2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
// These functions must write the new value back into the old value store,
|
||||
// and subsequently call the format specific emit* implementations. Note
|
||||
// that this file must be included in the format specific implementation, so
|
||||
// the emit* functions can be inlined for performance.
|
||||
|
||||
template <> //
|
||||
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullBit(uint32_t* oldp, CData newval) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitBit(code, newval);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullCData(uint32_t* oldp, CData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitCData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullSData(uint32_t* oldp, SData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitSData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullIData(uint32_t* oldp, IData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*oldp = newval; // Still copy even if not tracing so chg doesn't call full
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitIData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullQData(uint32_t* oldp, QData newval, int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*reinterpret_cast<QData*>(oldp) = newval;
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitQData(code, newval, bits);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullWData(uint32_t* oldp, const WData* newvalp,
|
||||
int bits) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
for (int i = 0; i < VL_WORDS_I(bits); ++i) oldp[i] = newvalp[i];
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
self()->emitWData(code, newvalp, bits);
|
||||
}
|
||||
|
||||
template <>
|
||||
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullDouble(uint32_t* oldp, double newval) {
|
||||
const uint32_t code = oldp - m_sigs_oldvalp;
|
||||
*reinterpret_cast<double*>(oldp) = newval;
|
||||
if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return;
|
||||
// cppcheck-suppress invalidPointerCast
|
||||
self()->emitDouble(code, newval);
|
||||
}
|
||||
|
||||
#endif // VL_CPPCHECK
|
||||
|
|
@ -289,7 +289,7 @@ public:
|
|||
// Can't just overload operator[] or provide a "at" reference to set,
|
||||
// because we need to be able to insert only when the value is set
|
||||
T_Value& at(int32_t index) {
|
||||
static T_Value s_throwAway;
|
||||
static VL_THREAD_LOCAL T_Value s_throwAway;
|
||||
// Needs to work for dynamic arrays, so does not use T_MaxSize
|
||||
if (VL_UNLIKELY(index < 0 || index >= m_deque.size())) {
|
||||
s_throwAway = atDefault();
|
||||
|
|
@ -300,7 +300,7 @@ public:
|
|||
}
|
||||
// Accessing. Verilog: v = assoc[index]
|
||||
const T_Value& at(int32_t index) const {
|
||||
static T_Value s_throwAway;
|
||||
static VL_THREAD_LOCAL T_Value s_throwAway;
|
||||
// Needs to work for dynamic arrays, so does not use T_MaxSize
|
||||
if (VL_UNLIKELY(index < 0 || index >= m_deque.size())) {
|
||||
return atDefault();
|
||||
|
|
|
|||
|
|
@ -62,12 +62,23 @@ constexpr unsigned VL_TRACE_MAX_VCD_CODE_SIZE = 5; // Maximum length of a VCD s
|
|||
// cache-lines.
|
||||
constexpr unsigned VL_TRACE_SUFFIX_ENTRY_SIZE = 8; // Size of a suffix entry
|
||||
|
||||
//=============================================================================
|
||||
// Utility functions: TODO: put these in a common place and share them.
|
||||
|
||||
template <size_t N> static size_t roundUpToMultipleOf(size_t value) {
|
||||
static_assert((N & (N - 1)) == 0, "'N' must be a power of 2");
|
||||
size_t mask = N - 1;
|
||||
return (value + mask) & ~mask;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Specialization of the generics for this trace format
|
||||
|
||||
#define VL_DERIVED_T VerilatedVcd
|
||||
#include "verilated_trace_imp.cpp"
|
||||
#undef VL_DERIVED_T
|
||||
#define VL_SUB_T VerilatedVcd
|
||||
#define VL_BUF_T VerilatedVcdBuffer
|
||||
#include "verilated_trace_imp.h"
|
||||
#undef VL_SUB_T
|
||||
#undef VL_BUF_T
|
||||
|
||||
//=============================================================================
|
||||
//=============================================================================
|
||||
|
|
@ -183,7 +194,7 @@ void VerilatedVcd::makeNameMap() {
|
|||
deleteNameMap();
|
||||
m_namemapp = new NameMap;
|
||||
|
||||
VerilatedTrace<VerilatedVcd>::traceInit();
|
||||
Super::traceInit();
|
||||
|
||||
// Though not speced, it's illegal to generate a vcd with signals
|
||||
// not under any module - it crashes at least two viewers.
|
||||
|
|
@ -218,13 +229,17 @@ VerilatedVcd::~VerilatedVcd() {
|
|||
if (m_wrBufp) VL_DO_CLEAR(delete[] m_wrBufp, m_wrBufp = nullptr);
|
||||
deleteNameMap();
|
||||
if (m_filep && m_fileNewed) VL_DO_CLEAR(delete m_filep, m_filep = nullptr);
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
assert(m_numBuffers == m_freeBuffers.size());
|
||||
for (auto& pair : m_freeBuffers) VL_DO_CLEAR(delete[] pair.first, pair.first = nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VerilatedVcd::closePrev() {
|
||||
// This function is on the flush() call path
|
||||
if (!isOpen()) return;
|
||||
|
||||
VerilatedTrace<VerilatedVcd>::flushBase();
|
||||
Super::flushBase();
|
||||
bufferFlush();
|
||||
m_isOpen = false;
|
||||
m_filep->close();
|
||||
|
|
@ -251,14 +266,14 @@ void VerilatedVcd::close() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|||
printStr(" $end\n");
|
||||
}
|
||||
closePrev();
|
||||
// closePrev() called VerilatedTrace<VerilatedVcd>::flush(), so we just
|
||||
// closePrev() called Super::flush(), so we just
|
||||
// need to shut down the tracing thread here.
|
||||
VerilatedTrace<VerilatedVcd>::closeBase();
|
||||
Super::closeBase();
|
||||
}
|
||||
|
||||
void VerilatedVcd::flush() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
||||
const VerilatedLockGuard lock{m_mutex};
|
||||
VerilatedTrace<VerilatedVcd>::flushBase();
|
||||
Super::flushBase();
|
||||
bufferFlush();
|
||||
}
|
||||
|
||||
|
|
@ -277,12 +292,12 @@ void VerilatedVcd::printQuad(uint64_t n) {
|
|||
printStr(buf);
|
||||
}
|
||||
|
||||
void VerilatedVcd::bufferResize(uint64_t minsize) {
|
||||
void VerilatedVcd::bufferResize(size_t minsize) {
|
||||
// minsize is size of largest write. We buffer at least 8 times as much data,
|
||||
// writing when we are 3/4 full (with thus 2*minsize remaining free)
|
||||
if (VL_UNLIKELY(minsize > m_wrChunkSize)) {
|
||||
const char* oldbufp = m_wrBufp;
|
||||
m_wrChunkSize = minsize * 2;
|
||||
m_wrChunkSize = roundUpToMultipleOf<1024>(minsize * 2);
|
||||
m_wrBufp = new char[m_wrChunkSize * 8];
|
||||
std::memcpy(m_wrBufp, oldbufp, m_writep - oldbufp);
|
||||
m_writep = m_wrBufp + (m_writep - oldbufp);
|
||||
|
|
@ -292,7 +307,7 @@ void VerilatedVcd::bufferResize(uint64_t minsize) {
|
|||
}
|
||||
|
||||
void VerilatedVcd::bufferFlush() VL_MT_UNSAFE_ONE {
|
||||
// This function can be called from the trace thread
|
||||
// This function can be called from the trace offload thread
|
||||
// This function is on the flush() call path
|
||||
// We add output data to m_writep.
|
||||
// When it gets nearly full we dump it using this routine which calls write()
|
||||
|
|
@ -463,14 +478,16 @@ void VerilatedVcd::declare(uint32_t code, const char* name, const char* wirep, b
|
|||
int arraynum, bool tri, bool bussed, int msb, int lsb) {
|
||||
const int bits = ((msb > lsb) ? (msb - lsb) : (lsb - msb)) + 1;
|
||||
|
||||
const bool enabled = VerilatedTrace<VerilatedVcd>::declCode(code, name, bits, tri);
|
||||
const bool enabled = Super::declCode(code, name, bits, tri);
|
||||
|
||||
if (m_suffixes.size() <= nextCode() * VL_TRACE_SUFFIX_ENTRY_SIZE) {
|
||||
m_suffixes.resize(nextCode() * VL_TRACE_SUFFIX_ENTRY_SIZE * 2, 0);
|
||||
}
|
||||
|
||||
// Make sure write buffer is large enough (one character per bit), plus header
|
||||
bufferResize(bits + 1024);
|
||||
// Keep upper bound on bytes a single signal cna emit into the buffer
|
||||
m_maxSignalBytes = std::max<size_t>(m_maxSignalBytes, bits + 32);
|
||||
// Make sure write buffer is large enough, plus header
|
||||
bufferResize(m_maxSignalBytes + 1024);
|
||||
|
||||
if (!enabled) return;
|
||||
|
||||
|
|
@ -562,26 +579,73 @@ void VerilatedVcd::declArray(uint32_t code, const char* name, bool array, int ar
|
|||
void VerilatedVcd::declDouble(uint32_t code, const char* name, bool array, int arraynum) {
|
||||
declare(code, name, "real", array, arraynum, false, false, 63, 0);
|
||||
}
|
||||
#ifdef VL_TRACE_VCD_OLD_API
|
||||
void VerilatedVcd::declTriBit(uint32_t code, const char* name, bool array, int arraynum) {
|
||||
declare(code, name, "wire", array, arraynum, true, false, 0, 0);
|
||||
}
|
||||
void VerilatedVcd::declTriBus(uint32_t code, const char* name, bool array, int arraynum, int msb,
|
||||
int lsb) {
|
||||
declare(code, name, "wire", array, arraynum, true, true, msb, lsb);
|
||||
}
|
||||
void VerilatedVcd::declTriQuad(uint32_t code, const char* name, bool array, int arraynum, int msb,
|
||||
int lsb) {
|
||||
declare(code, name, "wire", array, arraynum, true, true, msb, lsb);
|
||||
}
|
||||
void VerilatedVcd::declTriArray(uint32_t code, const char* name, bool array, int arraynum, int msb,
|
||||
int lsb) {
|
||||
declare(code, name, "wire", array, arraynum, true, true, msb, lsb);
|
||||
}
|
||||
#endif // VL_TRACE_VCD_OLD_API
|
||||
|
||||
//=============================================================================
|
||||
// Trace rendering prinitives
|
||||
// Get/commit trace buffer
|
||||
|
||||
VerilatedVcdBuffer* VerilatedVcd::getTraceBuffer() {
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
// Note: This is called from VeriltedVcd::dump, which already holds the lock
|
||||
// If no buffer available, allocate a new one
|
||||
if (m_freeBuffers.empty()) {
|
||||
constexpr size_t pageSize = 4096;
|
||||
// 4 * m_maxSignalBytes, so we can reserve 2 * m_maxSignalBytes at the end for safety
|
||||
size_t startingSize = roundUpToMultipleOf<pageSize>(4 * m_maxSignalBytes);
|
||||
m_freeBuffers.emplace_back(new char[startingSize], startingSize);
|
||||
++m_numBuffers;
|
||||
}
|
||||
// Grab a buffer
|
||||
const auto pair = m_freeBuffers.back();
|
||||
m_freeBuffers.pop_back();
|
||||
// Return the buffer
|
||||
return new VerilatedVcdBuffer{*this, pair.first, pair.second};
|
||||
#else
|
||||
return new VerilatedVcdBuffer{*this};
|
||||
#endif
|
||||
}
|
||||
|
||||
void VerilatedVcd::commitTraceBuffer(VerilatedVcdBuffer* bufp) {
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
// Note: This is called from VeriltedVcd::dump, which already holds the lock
|
||||
// Resize output buffer. Note, we use the full size of the trace buffer, as
|
||||
// this is a lot more stable than the actual occupancy of the trace buffer.
|
||||
// This helps us to avoid re-allocations due to small size changes.
|
||||
bufferResize(bufp->m_size);
|
||||
// Compute occupancy of buffer
|
||||
const size_t usedSize = bufp->m_writep - bufp->m_bufp;
|
||||
// Copy to output buffer
|
||||
std::memcpy(m_writep, bufp->m_bufp, usedSize);
|
||||
// Adjust write pointer
|
||||
m_writep += usedSize;
|
||||
// Flush if necessary
|
||||
bufferCheck();
|
||||
// Put buffer back on free list
|
||||
m_freeBuffers.emplace_back(bufp->m_bufp, bufp->m_size);
|
||||
#else
|
||||
// Needs adjusting for emitTimeChange
|
||||
m_writep = bufp->m_writep;
|
||||
#endif
|
||||
delete bufp;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedVcdBuffer implementation
|
||||
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
VerilatedVcdBuffer::VerilatedVcdBuffer(VerilatedVcd& owner, char* bufp, size_t size)
|
||||
: VerilatedTraceBuffer<VerilatedVcd, VerilatedVcdBuffer>{owner}
|
||||
, m_writep{bufp}
|
||||
, m_bufp{bufp}
|
||||
, m_size{size} {
|
||||
adjustGrowp();
|
||||
}
|
||||
#else
|
||||
VerilatedVcdBuffer::VerilatedVcdBuffer(VerilatedVcd& owner)
|
||||
: VerilatedTraceBuffer<VerilatedVcd, VerilatedVcdBuffer>{owner} {}
|
||||
#endif
|
||||
|
||||
//=============================================================================
|
||||
// Trace rendering primitives
|
||||
|
||||
static inline void
|
||||
VerilatedVcdCCopyAndAppendNewLine(char* writep, const char* suffixp) VL_ATTR_NO_SANITIZE_ALIGN;
|
||||
|
|
@ -606,26 +670,55 @@ static inline void VerilatedVcdCCopyAndAppendNewLine(char* writep, const char* s
|
|||
#endif
|
||||
}
|
||||
|
||||
void VerilatedVcd::finishLine(uint32_t code, char* writep) {
|
||||
const char* const suffixp = m_suffixes.data() + code * VL_TRACE_SUFFIX_ENTRY_SIZE;
|
||||
void VerilatedVcdBuffer::finishLine(uint32_t code, char* writep) {
|
||||
const char* const suffixp = m_suffixes + code * VL_TRACE_SUFFIX_ENTRY_SIZE;
|
||||
VL_DEBUG_IFDEF(assert(suffixp[0]););
|
||||
VerilatedVcdCCopyAndAppendNewLine(writep, suffixp);
|
||||
|
||||
// Now write back the write pointer incremented by the actual size of the
|
||||
// suffix, which was stored in the last byte of the suffix buffer entry.
|
||||
m_writep = writep + suffixp[VL_TRACE_SUFFIX_ENTRY_SIZE - 1];
|
||||
bufferCheck();
|
||||
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
// Double the size of the buffer if necessary
|
||||
if (VL_UNLIKELY(m_writep >= m_growp)) {
|
||||
// Compute occupied size of current buffer
|
||||
const size_t usedSize = m_writep - m_bufp;
|
||||
// We are always doubling the size
|
||||
m_size *= 2;
|
||||
// Allocate the new buffer
|
||||
char* const newBufp = new char[m_size];
|
||||
// Copy from current buffer to new buffer
|
||||
std::memcpy(newBufp, m_bufp, usedSize);
|
||||
// Delete current buffer
|
||||
delete[] m_bufp;
|
||||
// Make new buffer the current buffer
|
||||
m_bufp = newBufp;
|
||||
// Adjust write pointer
|
||||
m_writep = m_bufp + usedSize;
|
||||
// Adjust resize limit
|
||||
adjustGrowp();
|
||||
}
|
||||
#else
|
||||
// Flush the write buffer if there's not enough space left for new information
|
||||
// We only call this once per vector, so we need enough slop for a very wide "b###" line
|
||||
if (VL_UNLIKELY(m_writep > m_wrFlushp)) {
|
||||
m_owner.m_writep = m_writep;
|
||||
m_owner.bufferFlush();
|
||||
m_writep = m_owner.m_writep;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// emit* trace routines
|
||||
|
||||
// Note: emit* are only ever called from one place (full* in
|
||||
// verilated_trace_imp.cpp, which is included in this file at the top),
|
||||
// verilated_trace_imp.h, which is included in this file at the top),
|
||||
// so always inline them.
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedVcd::emitBit(uint32_t code, CData newval) {
|
||||
void VerilatedVcdBuffer::emitBit(uint32_t code, CData newval) {
|
||||
// Don't prefetch suffix as it's a bit too late;
|
||||
char* wp = m_writep;
|
||||
*wp++ = '0' | static_cast<char>(newval);
|
||||
|
|
@ -633,7 +726,7 @@ void VerilatedVcd::emitBit(uint32_t code, CData newval) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedVcd::emitCData(uint32_t code, CData newval, int bits) {
|
||||
void VerilatedVcdBuffer::emitCData(uint32_t code, CData newval, int bits) {
|
||||
char* wp = m_writep;
|
||||
*wp++ = 'b';
|
||||
cvtCDataToStr(wp, newval << (VL_BYTESIZE - bits));
|
||||
|
|
@ -641,7 +734,7 @@ void VerilatedVcd::emitCData(uint32_t code, CData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedVcd::emitSData(uint32_t code, SData newval, int bits) {
|
||||
void VerilatedVcdBuffer::emitSData(uint32_t code, SData newval, int bits) {
|
||||
char* wp = m_writep;
|
||||
*wp++ = 'b';
|
||||
cvtSDataToStr(wp, newval << (VL_SHORTSIZE - bits));
|
||||
|
|
@ -649,7 +742,7 @@ void VerilatedVcd::emitSData(uint32_t code, SData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedVcd::emitIData(uint32_t code, IData newval, int bits) {
|
||||
void VerilatedVcdBuffer::emitIData(uint32_t code, IData newval, int bits) {
|
||||
char* wp = m_writep;
|
||||
*wp++ = 'b';
|
||||
cvtIDataToStr(wp, newval << (VL_IDATASIZE - bits));
|
||||
|
|
@ -657,7 +750,7 @@ void VerilatedVcd::emitIData(uint32_t code, IData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedVcd::emitQData(uint32_t code, QData newval, int bits) {
|
||||
void VerilatedVcdBuffer::emitQData(uint32_t code, QData newval, int bits) {
|
||||
char* wp = m_writep;
|
||||
*wp++ = 'b';
|
||||
cvtQDataToStr(wp, newval << (VL_QUADSIZE - bits));
|
||||
|
|
@ -665,7 +758,7 @@ void VerilatedVcd::emitQData(uint32_t code, QData newval, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedVcd::emitWData(uint32_t code, const WData* newvalp, int bits) {
|
||||
void VerilatedVcdBuffer::emitWData(uint32_t code, const WData* newvalp, int bits) {
|
||||
int words = VL_WORDS_I(bits);
|
||||
char* wp = m_writep;
|
||||
*wp++ = 'b';
|
||||
|
|
@ -682,272 +775,10 @@ void VerilatedVcd::emitWData(uint32_t code, const WData* newvalp, int bits) {
|
|||
}
|
||||
|
||||
VL_ATTR_ALWINLINE
|
||||
void VerilatedVcd::emitDouble(uint32_t code, double newval) {
|
||||
void VerilatedVcdBuffer::emitDouble(uint32_t code, double newval) {
|
||||
char* wp = m_writep;
|
||||
// Buffer can't overflow before VL_SNPRINTF; we sized during declaration
|
||||
VL_SNPRINTF(wp, m_wrChunkSize, "r%.16g", newval);
|
||||
VL_SNPRINTF(wp, m_maxSignalBytes, "r%.16g", newval);
|
||||
wp += std::strlen(wp);
|
||||
finishLine(code, wp);
|
||||
}
|
||||
|
||||
#ifdef VL_TRACE_VCD_OLD_API
|
||||
|
||||
void VerilatedVcd::fullBit(uint32_t code, const uint32_t newval) {
|
||||
// Note the &1, so we don't require clean input -- makes more common no change case faster
|
||||
*oldp(code) = newval;
|
||||
*m_writep++ = ('0' + static_cast<char>(newval & 1));
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullBus(uint32_t code, const uint32_t newval, int bits) {
|
||||
*oldp(code) = newval;
|
||||
*m_writep++ = 'b';
|
||||
for (int bit = bits - 1; bit >= 0; --bit) {
|
||||
*m_writep++ = ((newval & (1L << bit)) ? '1' : '0');
|
||||
}
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullQuad(uint32_t code, const uint64_t newval, int bits) {
|
||||
(*(reinterpret_cast<uint64_t*>(oldp(code)))) = newval;
|
||||
*m_writep++ = 'b';
|
||||
for (int bit = bits - 1; bit >= 0; --bit) {
|
||||
*m_writep++ = ((newval & (1ULL << bit)) ? '1' : '0');
|
||||
}
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullArray(uint32_t code, const uint32_t* newval, int bits) {
|
||||
for (int word = 0; word < (((bits - 1) / 32) + 1); ++word) { oldp(code)[word] = newval[word]; }
|
||||
*m_writep++ = 'b';
|
||||
for (int bit = bits - 1; bit >= 0; --bit) {
|
||||
*m_writep++ = ((newval[(bit / 32)] & (1L << (bit & 0x1f))) ? '1' : '0');
|
||||
}
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullArray(uint32_t code, const uint64_t* newval, int bits) {
|
||||
for (int word = 0; word < (((bits - 1) / 64) + 1); ++word) { oldp(code)[word] = newval[word]; }
|
||||
*m_writep++ = 'b';
|
||||
for (int bit = bits - 1; bit >= 0; --bit) {
|
||||
*m_writep++ = ((newval[(bit / 64)] & (1ULL << (bit & 0x3f))) ? '1' : '0');
|
||||
}
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullTriBit(uint32_t code, const uint32_t newval, const uint32_t newtri) {
|
||||
oldp(code)[0] = newval;
|
||||
oldp(code)[1] = newtri;
|
||||
*m_writep++ = "01zz"[newval | (newtri << 1)];
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullTriBus(uint32_t code, const uint32_t newval, const uint32_t newtri,
|
||||
int bits) {
|
||||
oldp(code)[0] = newval;
|
||||
oldp(code)[1] = newtri;
|
||||
*m_writep++ = 'b';
|
||||
for (int bit = bits - 1; bit >= 0; --bit) {
|
||||
*m_writep++ = "01zz"[((newval >> bit) & 1) | (((newtri >> bit) & 1) << 1)];
|
||||
}
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullTriQuad(uint32_t code, const uint64_t newval, const uint64_t newtri,
|
||||
int bits) {
|
||||
(*(reinterpret_cast<uint64_t*>(oldp(code)))) = newval;
|
||||
(*(reinterpret_cast<uint64_t*>(oldp(code + 1)))) = newtri;
|
||||
*m_writep++ = 'b';
|
||||
for (int bit = bits - 1; bit >= 0; --bit) {
|
||||
*m_writep++ = "01zz"[((newval >> bit) & 1ULL) | (((newtri >> bit) & 1ULL) << 1ULL)];
|
||||
}
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullTriArray(uint32_t code, const uint32_t* newvalp, const uint32_t* newtrip,
|
||||
int bits) {
|
||||
for (int word = 0; word < (((bits - 1) / 32) + 1); ++word) {
|
||||
oldp(code)[word * 2] = newvalp[word];
|
||||
oldp(code)[word * 2 + 1] = newtrip[word];
|
||||
}
|
||||
*m_writep++ = 'b';
|
||||
for (int bit = bits - 1; bit >= 0; --bit) {
|
||||
uint32_t valbit = (newvalp[(bit / 32)] >> (bit & 0x1f)) & 1;
|
||||
uint32_t tribit = (newtrip[(bit / 32)] >> (bit & 0x1f)) & 1;
|
||||
*m_writep++ = "01zz"[valbit | (tribit << 1)];
|
||||
}
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
void VerilatedVcd::fullDouble(uint32_t code, const double newval) {
|
||||
// cppcheck-suppress invalidPointerCast
|
||||
(*(reinterpret_cast<double*>(oldp(code)))) = newval;
|
||||
// Buffer can't overflow before VL_SNPRINTF; we sized during declaration
|
||||
VL_SNPRINTF(m_writep, m_wrChunkSize, "r%.16g", newval);
|
||||
m_writep += std::strlen(m_writep);
|
||||
*m_writep++ = ' ';
|
||||
m_writep = writeCode(m_writep, code);
|
||||
*m_writep++ = '\n';
|
||||
bufferCheck();
|
||||
}
|
||||
|
||||
#endif // VL_TRACE_VCD_OLD_API
|
||||
|
||||
//======================================================================
|
||||
//======================================================================
|
||||
//======================================================================
|
||||
|
||||
#ifdef VERILATED_VCD_TEST
|
||||
#include <iostream>
|
||||
|
||||
extern void verilated_trace_imp_selftest();
|
||||
|
||||
uint32_t v1, v2, s1, s2[3];
|
||||
uint32_t tri96[3];
|
||||
uint32_t tri96__tri[3];
|
||||
uint64_t quad96[2];
|
||||
uint64_t tquad;
|
||||
uint64_t tquad__tri;
|
||||
uint8_t ch;
|
||||
uint64_t timestamp = 1;
|
||||
double doub = 0.0;
|
||||
float flo = 0.0f;
|
||||
|
||||
void vcdInit(void*, VerilatedVcd* vcdp, uint32_t) {
|
||||
vcdp->scopeEscape('.');
|
||||
vcdp->pushNamePrefix("top.");
|
||||
/**/ vcdp->declBus(0x2, "v1", -1, 0, 5, 1);
|
||||
/**/ vcdp->declBus(0x3, "v2", -1, 0, 6, 1);
|
||||
/**/ vcdp->pushNamePrefix("sub1.");
|
||||
/***/ vcdp->declBit(0x4, "s1", -1, 0);
|
||||
/***/ vcdp->declBit(0x5, "ch", -1, 0);
|
||||
/**/ vcdp->popNamePrefix();
|
||||
/**/ vcdp->pushNamePrefix("sub2.");
|
||||
/***/ vcdp->declArray(0x6, "s2", -1, 0, 40, 3);
|
||||
/**/ vcdp->popNamePrefix();
|
||||
vcdp->popNamePrefix();
|
||||
// Note need to add 3 for next code.
|
||||
vcdp->pushNamePrefix("top2.");
|
||||
/**/ vcdp->declBus(0x2, "t2v1", -1, 0, 4, 1);
|
||||
/**/ vcdp->declTriBit(0x10, "io1", -1, 0);
|
||||
/**/ vcdp->declTriBus(0x12, "io5", -1, 0, 4, 0);
|
||||
/**/ vcdp->declTriArray(0x16, "io96", -1, 0, 95, 0);
|
||||
/**/ // Note need to add 6 for next code.
|
||||
/**/ vcdp->declDouble(0x1c, "doub", -1, 0);
|
||||
/**/ // Note need to add 2 for next code.
|
||||
/**/ vcdp->declArray(0x20, "q2", -1, 0, 95, 0);
|
||||
/**/ // Note need to add 4 for next code.
|
||||
/**/ vcdp->declTriQuad(0x24, "tq", -1, 0, 63, 0);
|
||||
/**/ // Note need to add 4 for next code.
|
||||
vcdp->popNamePrefix();
|
||||
}
|
||||
|
||||
void vcdFull(void*, VerilatedVcd* vcdp) {
|
||||
vcdp->fullBus(0x2, v1, 5);
|
||||
vcdp->fullBus(0x3, v2, 7);
|
||||
vcdp->fullBit(0x4, s1);
|
||||
vcdp->fullBus(0x5, ch, 2);
|
||||
vcdp->fullArray(0x6, &s2[0], 38);
|
||||
vcdp->fullTriBit(0x10, tri96[0] & 1, tri96__tri[0] & 1);
|
||||
vcdp->fullTriBus(0x12, tri96[0] & 0x1f, tri96__tri[0] & 0x1f, 5);
|
||||
vcdp->fullTriArray(0x16, tri96, tri96__tri, 96);
|
||||
vcdp->fullDouble(0x1c, doub);
|
||||
vcdp->fullArray(0x20, &quad96[0], 96);
|
||||
vcdp->fullTriQuad(0x24, tquad, tquad__tri, 64);
|
||||
}
|
||||
|
||||
void vcdChange(void*, VerilatedVcd* vcdp) {
|
||||
vcdp->chgBus(0x2, v1, 5);
|
||||
vcdp->chgBus(0x3, v2, 7);
|
||||
vcdp->chgBit(0x4, s1);
|
||||
vcdp->chgBus(0x5, ch, 2);
|
||||
vcdp->chgArray(0x6, &s2[0], 38);
|
||||
vcdp->chgTriBit(0x10, tri96[0] & 1, tri96__tri[0] & 1);
|
||||
vcdp->chgTriBus(0x12, tri96[0] & 0x1f, tri96__tri[0] & 0x1f, 5);
|
||||
vcdp->chgTriArray(0x16, tri96, tri96__tri, 96);
|
||||
vcdp->chgDouble(0x1c, doub);
|
||||
vcdp->chgArray(0x20, &quad96[0], 96);
|
||||
vcdp->chgTriQuad(0x24, tquad, tquad__tri, 64);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
void vcdTestMain(const char* filenamep) {
|
||||
verilated_trace_imp_selftest();
|
||||
|
||||
v1 = v2 = s1 = 0;
|
||||
s2[0] = s2[1] = s2[2] = 0;
|
||||
tri96[2] = tri96[1] = tri96[0] = 0;
|
||||
tri96__tri[2] = tri96__tri[1] = tri96__tri[0] = ~0;
|
||||
quad96[1] = quad96[0] = 0;
|
||||
ch = 0;
|
||||
doub = 0;
|
||||
tquad = tquad__tri = 0;
|
||||
{
|
||||
VerilatedVcdC* vcdp = new VerilatedVcdC;
|
||||
vcdp->evcd(true);
|
||||
vcdp->set_time_unit("1ms");
|
||||
vcdp->set_time_unit(std::string{"1ms"});
|
||||
vcdp->set_time_resolution("1ns");
|
||||
vcdp->set_time_resolution(std::string{"1ns"});
|
||||
vcdp->spTrace()->addInitCb(&vcdInit, 0);
|
||||
vcdp->spTrace()->addFullCb(&vcdFull, 0);
|
||||
vcdp->spTrace()->addChgCb(&vcdChange, 0);
|
||||
vcdp->open(filenamep);
|
||||
// Dumping
|
||||
vcdp->dump(++timestamp);
|
||||
v1 = 0xfff;
|
||||
tri96[2] = 4; tri96[1] = 2; tri96[0] = 1;
|
||||
tri96__tri[2] = tri96__tri[1] = tri96__tri[0] = ~0; // Still tri
|
||||
quad96[1] = 0xffffffff; quad96[0] = 0;
|
||||
doub = 1.5;
|
||||
flo = 1.4f;
|
||||
vcdp->dump(++timestamp);
|
||||
v2 = 0x1;
|
||||
s2[1] = 2;
|
||||
tri96__tri[2] = tri96__tri[1] = tri96__tri[0] = 0; // enable w/o data change
|
||||
quad96[1] = 0; quad96[0] = ~0;
|
||||
doub = -1.66e13;
|
||||
flo = 0.123f;
|
||||
tquad = 0x00ff00ff00ff00ffULL;
|
||||
tquad__tri = 0x0000fffff0000ffffULL;
|
||||
vcdp->dump(++timestamp);
|
||||
ch = 2;
|
||||
tri96[2] = ~4; tri96[1] = ~2; tri96[0] = ~1;
|
||||
doub = -3.33e-13;
|
||||
vcdp->dump(++timestamp);
|
||||
vcdp->dump(++timestamp);
|
||||
# ifdef VERILATED_VCD_TEST_64BIT
|
||||
const uint64_t bytesPerDump = 15ULL;
|
||||
for (uint64_t i = 0; i < ((1ULL << 32) / bytesPerDump); i++) {
|
||||
v1 = i;
|
||||
vcdp->dump(++timestamp);
|
||||
}
|
||||
# endif
|
||||
vcdp->close();
|
||||
VL_DO_CLEAR(delete vcdp, vcdp = nullptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
//********************************************************************
|
||||
// ;compile-command: "v4make test_regress/t/t_trace_c_api.pl"
|
||||
//
|
||||
// Local Variables:
|
||||
// End:
|
||||
|
|
|
|||
|
|
@ -28,39 +28,20 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class VerilatedVcd;
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedFile
|
||||
/// Class representing a file to write to. These virtual methods can be
|
||||
/// overrode for e.g. socket I/O.
|
||||
|
||||
class VerilatedVcdFile VL_NOT_FINAL {
|
||||
private:
|
||||
int m_fd = 0; // File descriptor we're writing to
|
||||
public:
|
||||
// METHODS
|
||||
/// Construct a (as yet) closed file
|
||||
VerilatedVcdFile() = default;
|
||||
/// Close and destruct
|
||||
virtual ~VerilatedVcdFile() = default;
|
||||
/// Open a file with given filename
|
||||
virtual bool open(const std::string& name) VL_MT_UNSAFE;
|
||||
/// Close object's file
|
||||
virtual void close() VL_MT_UNSAFE;
|
||||
/// Write data to file (if it is open)
|
||||
virtual ssize_t write(const char* bufp, ssize_t len) VL_MT_UNSAFE;
|
||||
};
|
||||
class VerilatedVcdBuffer;
|
||||
class VerilatedVcdFile;
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedVcd
|
||||
// Base class to create a Verilator VCD dump
|
||||
// This is an internally used class - see VerilatedVcdC for what to call from applications
|
||||
|
||||
class VerilatedVcd VL_NOT_FINAL : public VerilatedTrace<VerilatedVcd> {
|
||||
class VerilatedVcd VL_NOT_FINAL : public VerilatedTrace<VerilatedVcd, VerilatedVcdBuffer> {
|
||||
public:
|
||||
using Super = VerilatedTrace<VerilatedVcd, VerilatedVcdBuffer>;
|
||||
|
||||
private:
|
||||
// Give the superclass access to private bits (to avoid virtual functions)
|
||||
friend class VerilatedTrace<VerilatedVcd>;
|
||||
friend Buffer; // Give the buffer access to the private bits
|
||||
|
||||
//=========================================================================
|
||||
// VCD specific internals
|
||||
|
|
@ -74,9 +55,10 @@ private:
|
|||
int m_modDepth = 0; // Depth of module hierarchy
|
||||
|
||||
char* m_wrBufp; // Output buffer
|
||||
const char* m_wrFlushp; // Output buffer flush trigger location
|
||||
char* m_wrFlushp; // Output buffer flush trigger location
|
||||
char* m_writep; // Write pointer into output buffer
|
||||
uint64_t m_wrChunkSize; // Output buffer size
|
||||
size_t m_wrChunkSize; // Output buffer size
|
||||
size_t m_maxSignalBytes = 0; // Upper bound on number of bytes a single signal can generate
|
||||
uint64_t m_wroteBytes = 0; // Number of bytes written to this file
|
||||
|
||||
std::vector<char> m_suffixes; // VCD line end string codes + metadata
|
||||
|
|
@ -84,7 +66,13 @@ private:
|
|||
using NameMap = std::map<const std::string, const std::string>;
|
||||
NameMap* m_namemapp = nullptr; // List of names for the header
|
||||
|
||||
void bufferResize(uint64_t minsize);
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
// Vector of free trace buffers as (pointer, size) pairs.
|
||||
std::vector<std::pair<char*, size_t>> m_freeBuffers;
|
||||
size_t m_numBuffers = 0; // Number of trace buffers allocated
|
||||
#endif
|
||||
|
||||
void bufferResize(size_t minsize);
|
||||
void bufferFlush() VL_MT_UNSAFE_ONE;
|
||||
inline void bufferCheck() {
|
||||
// Flush the write buffer if there's not enough space left for new information
|
||||
|
|
@ -107,8 +95,6 @@ private:
|
|||
|
||||
static char* writeCode(char* writep, uint32_t code);
|
||||
|
||||
void finishLine(uint32_t code, char* writep);
|
||||
|
||||
// CONSTRUCTORS
|
||||
VL_UNCOPYABLE(VerilatedVcd);
|
||||
|
||||
|
|
@ -116,27 +102,22 @@ protected:
|
|||
//=========================================================================
|
||||
// Implementation of VerilatedTrace interface
|
||||
|
||||
// Implementations of protected virtual methods for VerilatedTrace
|
||||
// Called when the trace moves forward to a new time point
|
||||
virtual void emitTimeChange(uint64_t timeui) override;
|
||||
|
||||
// Hooks called from VerilatedTrace
|
||||
virtual bool preFullDump() override { return isOpen(); }
|
||||
virtual bool preChangeDump() override;
|
||||
|
||||
// Implementations of duck-typed methods for VerilatedTrace. These are
|
||||
// called from only one place (namely full*) so always inline them.
|
||||
inline void emitBit(uint32_t code, CData newval);
|
||||
inline void emitCData(uint32_t code, CData newval, int bits);
|
||||
inline void emitSData(uint32_t code, SData newval, int bits);
|
||||
inline void emitIData(uint32_t code, IData newval, int bits);
|
||||
inline void emitQData(uint32_t code, QData newval, int bits);
|
||||
inline void emitWData(uint32_t code, const WData* newvalp, int bits);
|
||||
inline void emitDouble(uint32_t code, double newval);
|
||||
// Trace buffer management
|
||||
virtual VerilatedVcdBuffer* getTraceBuffer() override;
|
||||
virtual void commitTraceBuffer(VerilatedVcdBuffer*) override;
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// External interface to client code
|
||||
|
||||
// CONSTRUCTOR
|
||||
explicit VerilatedVcd(VerilatedVcdFile* filep = nullptr);
|
||||
~VerilatedVcd();
|
||||
|
||||
|
|
@ -144,7 +125,7 @@ public:
|
|||
// Set size in megabytes after which new file should be created
|
||||
void rolloverMB(uint64_t rolloverMB) { m_rolloverMB = rolloverMB; }
|
||||
|
||||
// METHODS
|
||||
// METHODS - All must be thread safe
|
||||
// Open the file; call isOpen() to see if errors
|
||||
void open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex);
|
||||
// Open next data-only file
|
||||
|
|
@ -164,168 +145,95 @@ public:
|
|||
void declQuad(uint32_t code, const char* name, bool array, int arraynum, int msb, int lsb);
|
||||
void declArray(uint32_t code, const char* name, bool array, int arraynum, int msb, int lsb);
|
||||
void declDouble(uint32_t code, const char* name, bool array, int arraynum);
|
||||
|
||||
#ifdef VL_TRACE_VCD_OLD_API
|
||||
//=========================================================================
|
||||
// Note: These are only for testing for backward compatibility with foreign
|
||||
// code and is not used by Verilator. Do not use these as there is no
|
||||
// guarantee of functionality.
|
||||
|
||||
void declTriBit(uint32_t code, const char* name, bool array, int arraynum);
|
||||
void declTriBus(uint32_t code, const char* name, bool array, int arraynum, int msb, int lsb);
|
||||
void declTriQuad(uint32_t code, const char* name, bool array, int arraynum, int msb, int lsb);
|
||||
void declTriArray(uint32_t code, const char* name, bool array, int arraynum, int msb, int lsb);
|
||||
|
||||
void fullBit(uint32_t* oldp, CData newval) { fullBit(oldp - this->oldp(0), newval); }
|
||||
void fullCData(uint32_t* oldp, CData newval, int bits) {
|
||||
fullBus(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
void fullSData(uint32_t* oldp, SData newval, int bits) {
|
||||
fullBus(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
void fullIData(uint32_t* oldp, IData newval, int bits) {
|
||||
fullBus(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
void fullQData(uint32_t* oldp, QData newval, int bits) {
|
||||
fullQuad(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
void fullWData(uint32_t* oldp, const WData* newvalp, int bits) {
|
||||
fullArray(oldp - this->oldp(0), newvalp, bits);
|
||||
}
|
||||
void fullDouble(uint32_t* oldp, double newval) { fullDouble(oldp - this->oldp(0), newval); }
|
||||
|
||||
inline void chgBit(uint32_t* oldp, CData newval) { chgBit(oldp - this->oldp(0), newval); }
|
||||
inline void chgCData(uint32_t* oldp, CData newval, int bits) {
|
||||
chgBus(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
inline void chgSData(uint32_t* oldp, SData newval, int bits) {
|
||||
chgBus(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
inline void chgIData(uint32_t* oldp, IData newval, int bits) {
|
||||
chgBus(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
inline void chgQData(uint32_t* oldp, QData newval, int bits) {
|
||||
chgQuad(oldp - this->oldp(0), newval, bits);
|
||||
}
|
||||
inline void chgWData(uint32_t* oldp, const WData* newvalp, int bits) {
|
||||
chgArray(oldp - this->oldp(0), newvalp, bits);
|
||||
}
|
||||
inline void chgDouble(uint32_t* oldp, double newval) {
|
||||
chgDouble(oldp - this->oldp(0), newval);
|
||||
}
|
||||
|
||||
// Inside dumping routines, dump one signal, faster when not inlined
|
||||
// due to code size reduction.
|
||||
void fullBit(uint32_t code, const uint32_t newval);
|
||||
void fullBus(uint32_t code, const uint32_t newval, int bits);
|
||||
void fullQuad(uint32_t code, const uint64_t newval, int bits);
|
||||
void fullArray(uint32_t code, const uint32_t* newvalp, int bits);
|
||||
void fullArray(uint32_t code, const uint64_t* newvalp, int bits);
|
||||
void fullTriBit(uint32_t code, const uint32_t newval, const uint32_t newtri);
|
||||
void fullTriBus(uint32_t code, const uint32_t newval, const uint32_t newtri, int bits);
|
||||
void fullTriQuad(uint32_t code, const uint64_t newval, const uint64_t newtri, int bits);
|
||||
void fullTriArray(uint32_t code, const uint32_t* newvalp, const uint32_t* newtrip, int bits);
|
||||
void fullDouble(uint32_t code, const double newval);
|
||||
|
||||
// Inside dumping routines, dump one signal if it has changed.
|
||||
// We do want to inline these to avoid calls when the value did not change.
|
||||
inline void chgBit(uint32_t code, const uint32_t newval) {
|
||||
const uint32_t diff = oldp(code)[0] ^ newval;
|
||||
if (VL_UNLIKELY(diff)) fullBit(code, newval);
|
||||
}
|
||||
inline void chgBus(uint32_t code, const uint32_t newval, int bits) {
|
||||
const uint32_t diff = oldp(code)[0] ^ newval;
|
||||
if (VL_UNLIKELY(diff)) {
|
||||
if (VL_UNLIKELY(bits == 32 || (diff & ((1U << bits) - 1)))) {
|
||||
fullBus(code, newval, bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgQuad(uint32_t code, const uint64_t newval, int bits) {
|
||||
const uint64_t diff = (*(reinterpret_cast<uint64_t*>(oldp(code)))) ^ newval;
|
||||
if (VL_UNLIKELY(diff)) {
|
||||
if (VL_UNLIKELY(bits == 64 || (diff & ((1ULL << bits) - 1)))) {
|
||||
fullQuad(code, newval, bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgArray(uint32_t code, const uint32_t* newvalp, int bits) {
|
||||
for (int word = 0; word < (((bits - 1) / 32) + 1); ++word) {
|
||||
if (VL_UNLIKELY(oldp(code)[word] ^ newvalp[word])) {
|
||||
fullArray(code, newvalp, bits);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgArray(uint32_t code, const uint64_t* newvalp, int bits) {
|
||||
for (int word = 0; word < (((bits - 1) / 64) + 1); ++word) {
|
||||
if (VL_UNLIKELY(*(reinterpret_cast<uint64_t*>(oldp(code + 2 * word)))
|
||||
^ newvalp[word])) {
|
||||
fullArray(code, newvalp, bits);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgTriBit(uint32_t code, const uint32_t newval, const uint32_t newtri) {
|
||||
const uint32_t diff = ((oldp(code)[0] ^ newval) | (oldp(code)[1] ^ newtri));
|
||||
if (VL_UNLIKELY(diff)) {
|
||||
// Verilator 3.510 and newer provide clean input, so the below
|
||||
// is only for back compatibility
|
||||
if (VL_UNLIKELY(diff & 1)) { // Change after clean?
|
||||
fullTriBit(code, newval, newtri);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgTriBus(uint32_t code, const uint32_t newval, const uint32_t newtri, int bits) {
|
||||
const uint32_t diff = ((oldp(code)[0] ^ newval) | (oldp(code)[1] ^ newtri));
|
||||
if (VL_UNLIKELY(diff)) {
|
||||
if (VL_UNLIKELY(bits == 32 || (diff & ((1U << bits) - 1)))) {
|
||||
fullTriBus(code, newval, newtri, bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgTriQuad(uint32_t code, const uint64_t newval, const uint64_t newtri, int bits) {
|
||||
const uint64_t diff = (((*(reinterpret_cast<uint64_t*>(oldp(code)))) ^ newval)
|
||||
| ((*(reinterpret_cast<uint64_t*>(oldp(code + 1)))) ^ newtri));
|
||||
if (VL_UNLIKELY(diff)) {
|
||||
if (VL_UNLIKELY(bits == 64 || (diff & ((1ULL << bits) - 1)))) {
|
||||
fullTriQuad(code, newval, newtri, bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgTriArray(uint32_t code, const uint32_t* newvalp, const uint32_t* newtrip,
|
||||
int bits) {
|
||||
for (int word = 0; word < (((bits - 1) / 32) + 1); ++word) {
|
||||
if (VL_UNLIKELY((oldp(code)[word * 2] ^ newvalp[word])
|
||||
| (oldp(code)[word * 2 + 1] ^ newtrip[word]))) {
|
||||
fullTriArray(code, newvalp, newtrip, bits);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void chgDouble(uint32_t code, const double newval) {
|
||||
// cppcheck-suppress invalidPointerCast
|
||||
if (VL_UNLIKELY((*(reinterpret_cast<double*>(oldp(code)))) != newval)) {
|
||||
fullDouble(code, newval);
|
||||
}
|
||||
}
|
||||
|
||||
// METHODS
|
||||
// Old/standalone API only
|
||||
void evcd(bool flag) { m_evcd = flag; }
|
||||
#endif // VL_TRACE_VCD_OLD_API
|
||||
};
|
||||
|
||||
#ifndef DOXYGEN
|
||||
// Declare specializations here they are used in VerilatedVcdC just below
|
||||
template <> void VerilatedTrace<VerilatedVcd>::dump(uint64_t timeui);
|
||||
template <> void VerilatedTrace<VerilatedVcd>::set_time_unit(const char* unitp);
|
||||
template <> void VerilatedTrace<VerilatedVcd>::set_time_unit(const std::string& unit);
|
||||
template <> void VerilatedTrace<VerilatedVcd>::set_time_resolution(const char* unitp);
|
||||
template <> void VerilatedTrace<VerilatedVcd>::set_time_resolution(const std::string& unit);
|
||||
template <> void VerilatedTrace<VerilatedVcd>::dumpvars(int level, const std::string& hier);
|
||||
// Declare specialization here as it's used in VerilatedFstC just below
|
||||
template <> void VerilatedVcd::Super::dump(uint64_t time);
|
||||
template <> void VerilatedVcd::Super::set_time_unit(const char* unitp);
|
||||
template <> void VerilatedVcd::Super::set_time_unit(const std::string& unit);
|
||||
template <> void VerilatedVcd::Super::set_time_resolution(const char* unitp);
|
||||
template <> void VerilatedVcd::Super::set_time_resolution(const std::string& unit);
|
||||
template <> void VerilatedVcd::Super::dumpvars(int level, const std::string& hier);
|
||||
#endif // DOXYGEN
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedVcdBuffer
|
||||
|
||||
class VerilatedVcdBuffer final : public VerilatedTraceBuffer<VerilatedVcd, VerilatedVcdBuffer> {
|
||||
// Give the trace file access to the private bits
|
||||
friend VerilatedVcd;
|
||||
friend VerilatedVcd::Super;
|
||||
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
char* m_writep; // Write pointer into m_bufp
|
||||
char* m_bufp; // The beginning of the trace buffer
|
||||
size_t m_size; // The size of the buffer at m_bufp
|
||||
char* m_growp; // Resize limit pointer
|
||||
#else
|
||||
char* m_writep = m_owner.m_writep; // Write pointer into output buffer
|
||||
char* const m_wrFlushp = m_owner.m_wrFlushp; // Output buffer flush trigger location
|
||||
#endif
|
||||
|
||||
// VCD line end string codes + metadata
|
||||
const char* const m_suffixes = m_owner.m_suffixes.data();
|
||||
// The maximum number of bytes a single signal can emit
|
||||
const size_t m_maxSignalBytes = m_owner.m_maxSignalBytes;
|
||||
|
||||
void finishLine(uint32_t code, char* writep);
|
||||
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
void adjustGrowp() {
|
||||
m_growp = (m_bufp + m_size) - (2 * m_maxSignalBytes);
|
||||
assert(m_growp >= m_bufp + m_maxSignalBytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
// CONSTRUCTOR
|
||||
#ifdef VL_TRACE_PARALLEL
|
||||
explicit VerilatedVcdBuffer(VerilatedVcd& owner, char* bufp, size_t size);
|
||||
#else
|
||||
explicit VerilatedVcdBuffer(VerilatedVcd& owner);
|
||||
#endif
|
||||
~VerilatedVcdBuffer() = default;
|
||||
|
||||
//=========================================================================
|
||||
// Implementation of VerilatedTraceBuffer interface
|
||||
|
||||
// Implementations of duck-typed methods for VerilatedTraceBuffer. These are
|
||||
// called from only one place (the full* methods), so always inline them.
|
||||
VL_ATTR_ALWINLINE inline void emitBit(uint32_t code, CData newval);
|
||||
VL_ATTR_ALWINLINE inline void emitCData(uint32_t code, CData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitSData(uint32_t code, SData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitIData(uint32_t code, IData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitQData(uint32_t code, QData newval, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitWData(uint32_t code, const WData* newvalp, int bits);
|
||||
VL_ATTR_ALWINLINE inline void emitDouble(uint32_t code, double newval);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedFile
|
||||
/// Class representing a file to write to. These virtual methods can be
|
||||
/// overrode for e.g. socket I/O.
|
||||
|
||||
class VerilatedVcdFile VL_NOT_FINAL {
|
||||
private:
|
||||
int m_fd = 0; // File descriptor we're writing to
|
||||
public:
|
||||
// METHODS
|
||||
/// Construct a (as yet) closed file
|
||||
VerilatedVcdFile() = default;
|
||||
/// Close and destruct
|
||||
virtual ~VerilatedVcdFile() = default;
|
||||
/// Open a file with given filename
|
||||
virtual bool open(const std::string& name) VL_MT_UNSAFE;
|
||||
/// Close object's file
|
||||
virtual void close() VL_MT_UNSAFE;
|
||||
/// Write data to file (if it is open)
|
||||
virtual ssize_t write(const char* bufp, ssize_t len) VL_MT_UNSAFE;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VerilatedVcdC
|
||||
/// Class representing a VCD dump file in C standalone (no SystemC)
|
||||
|
|
@ -396,16 +304,6 @@ public:
|
|||
|
||||
// Internal class access
|
||||
inline VerilatedVcd* spTrace() { return &m_sptrace; }
|
||||
|
||||
#ifdef VL_TRACE_VCD_OLD_API
|
||||
//=========================================================================
|
||||
// Note: These are only for testing for backward compatibility with foreign
|
||||
// code and is not used by Verilator. Do not use these as there is no
|
||||
// guarantee of functionality.
|
||||
|
||||
// Use evcd format
|
||||
void evcd(bool flag) VL_MT_UNSAFE_ONE { m_sptrace.evcd(flag); }
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // guard
|
||||
|
|
|
|||
|
|
@ -81,8 +81,9 @@ public:
|
|||
// To simplify our free list, we use a size large enough for all derived types
|
||||
// We reserve word zero for the next pointer, as that's safer in case a
|
||||
// dangling reference to the original remains around.
|
||||
static const size_t chunk = 96;
|
||||
if (VL_UNCOVERABLE(size > chunk)) VL_FATAL_MT(__FILE__, __LINE__, "", "increase chunk");
|
||||
static constexpr size_t CHUNK_SIZE = 96;
|
||||
if (VL_UNCOVERABLE(size > CHUNK_SIZE))
|
||||
VL_FATAL_MT(__FILE__, __LINE__, "", "increase CHUNK_SIZE");
|
||||
if (VL_LIKELY(t_freeHead)) {
|
||||
uint8_t* const newp = t_freeHead;
|
||||
t_freeHead = *(reinterpret_cast<uint8_t**>(newp));
|
||||
|
|
@ -90,7 +91,7 @@ public:
|
|||
return newp + 8;
|
||||
}
|
||||
// +8: 8 bytes for next
|
||||
uint8_t* newp = reinterpret_cast<uint8_t*>(::operator new(chunk + 8));
|
||||
uint8_t* newp = reinterpret_cast<uint8_t*>(::operator new(CHUNK_SIZE + 8));
|
||||
*(reinterpret_cast<uint32_t*>(newp)) = activeMagic();
|
||||
return newp + 8;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#ifdef __GNUC__
|
||||
# define VL_ATTR_ALIGNED(alignment) __attribute__((aligned(alignment)))
|
||||
# define VL_ATTR_ALWINLINE __attribute__((always_inline))
|
||||
# define VL_ATTR_NOINLINE __attribute__((noinline))
|
||||
# define VL_ATTR_COLD __attribute__((cold))
|
||||
# define VL_ATTR_HOT __attribute__((hot))
|
||||
# define VL_ATTR_NORETURN __attribute__((noreturn))
|
||||
|
|
@ -82,6 +83,9 @@
|
|||
#ifndef VL_ATTR_ALWINLINE
|
||||
# define VL_ATTR_ALWINLINE ///< Attribute to inline, even when not optimizing
|
||||
#endif
|
||||
#ifndef VL_ATTR_NOINLINE
|
||||
# define VL_ATTR_NOINLINE ///< Attribute to never inline, even when optimizing
|
||||
#endif
|
||||
#ifndef VL_ATTR_COLD
|
||||
# define VL_ATTR_COLD ///< Attribute that function is rarely executed
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ public:
|
|||
iterate(nodep);
|
||||
m_graph.latchCheck(nodep, kwd == VAlwaysKwd::ALWAYS_LATCH);
|
||||
}
|
||||
virtual ~ActiveLatchCheckVisitor() {}
|
||||
virtual ~ActiveLatchCheckVisitor() = default;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
|
|
|
|||
|
|
@ -758,7 +758,7 @@ void AstNode::deleteTree() {
|
|||
#ifdef VL_LEAK_CHECKS
|
||||
void* AstNode::operator new(size_t size) {
|
||||
// Optimization note: Aligning to cache line is a loss, due to lost packing
|
||||
const AstNode* const objp = static_cast<AstNode*>(::operator new(size));
|
||||
AstNode* const objp = static_cast<AstNode*>(::operator new(size));
|
||||
V3Broken::addNewed(objp);
|
||||
return objp;
|
||||
}
|
||||
|
|
|
|||
28
src/V3Ast.h
28
src/V3Ast.h
|
|
@ -2021,11 +2021,12 @@ private:
|
|||
return Default;
|
||||
}
|
||||
|
||||
template <typename T_Node> constexpr static void checkTypeParameter() {
|
||||
template <typename T_Node> constexpr static bool checkTypeParameter() {
|
||||
static_assert(!std::is_const<T_Node>::value,
|
||||
"Type parameter 'T_Node' should not be const qualified");
|
||||
static_assert(std::is_base_of<AstNode, T_Node>::value,
|
||||
"Type parameter 'T_Node' must be a subtype of AstNode");
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -2035,25 +2036,25 @@ public:
|
|||
// operation function in 'foreach' should be completely predictable by branch target caches in
|
||||
// modern CPUs, while it is basically unpredictable for VNVisitor.
|
||||
template <typename T_Node> void foreach (std::function<void(T_Node*)> f) {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
foreachImpl<T_Node, /* VisitNext: */ false>(this, f);
|
||||
}
|
||||
|
||||
// Same as above, but for 'const' nodes
|
||||
template <typename T_Node> void foreach (std::function<void(const T_Node*)> f) const {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
foreachImpl<const T_Node, /* VisitNext: */ false>(this, f);
|
||||
}
|
||||
|
||||
// Same as 'foreach' but also follows 'this->nextp()'
|
||||
template <typename T_Node> void foreachAndNext(std::function<void(T_Node*)> f) {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
foreachImpl<T_Node, /* VisitNext: */ true>(this, f);
|
||||
}
|
||||
|
||||
// Same as 'foreach' but also follows 'this->nextp()'
|
||||
template <typename T_Node> void foreachAndNext(std::function<void(const T_Node*)> f) const {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
foreachImpl<const T_Node, /* VisitNext: */ true>(this, f);
|
||||
}
|
||||
|
||||
|
|
@ -2062,13 +2063,13 @@ public:
|
|||
// present. Traversal is performed in some arbitrary order and is terminated as soon as the
|
||||
// result can be determined.
|
||||
template <typename T_Node> bool exists(std::function<bool(T_Node*)> p) {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
return predicateImpl<T_Node, /* Default: */ false, /* VisitNext: */ false>(this, p);
|
||||
}
|
||||
|
||||
// Same as above, but for 'const' nodes
|
||||
template <typename T_Node> void exists(std::function<bool(const T_Node*)> p) const {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
return predicateImpl<const T_Node, /* Default: */ false, /* VisitNext: */ false>(this, p);
|
||||
}
|
||||
|
||||
|
|
@ -2077,13 +2078,13 @@ public:
|
|||
// present. Traversal is performed in some arbitrary order and is terminated as soon as the
|
||||
// result can be determined.
|
||||
template <typename T_Node> bool forall(std::function<bool(T_Node*)> p) {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
return predicateImpl<T_Node, /* Default: */ true, /* VisitNext: */ false>(this, p);
|
||||
}
|
||||
|
||||
// Same as above, but for 'const' nodes
|
||||
template <typename T_Node> void forall(std::function<bool(const T_Node*)> p) const {
|
||||
checkTypeParameter<T_Node>();
|
||||
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
|
||||
return predicateImpl<const T_Node, /* Default: */ true, /* VisitNext: */ false>(this, p);
|
||||
}
|
||||
|
||||
|
|
@ -2150,7 +2151,7 @@ static_assert(sizeof(VNRef<AstNode>) == sizeof(std::reference_wrapper<AstNode>),
|
|||
// without having to copy nodes into the collections.
|
||||
|
||||
// Forward declaration to avoid including V3Hasher.h which needs V3Ast.h (this file).
|
||||
size_t V3HasherUncachedHash(AstNode&);
|
||||
size_t V3HasherUncachedHash(const AstNode&);
|
||||
|
||||
// Specialization of std::hash for VNRef
|
||||
template <typename T_Node> //
|
||||
|
|
@ -2488,10 +2489,12 @@ public:
|
|||
|
||||
class AstNodeAssign VL_NOT_FINAL : public AstNodeStmt {
|
||||
protected:
|
||||
AstNodeAssign(VNType t, FileLine* fl, AstNode* lhsp, AstNode* rhsp)
|
||||
AstNodeAssign(VNType t, FileLine* fl, AstNode* lhsp, AstNode* rhsp,
|
||||
AstNode* timingControlp = nullptr)
|
||||
: AstNodeStmt{t, fl} {
|
||||
setOp1p(rhsp);
|
||||
setOp2p(lhsp);
|
||||
addNOp3p(timingControlp);
|
||||
dtypeFrom(lhsp);
|
||||
}
|
||||
|
||||
|
|
@ -2502,6 +2505,9 @@ public:
|
|||
// So iteration hits the RHS which is "earlier" in execution order, it's op1, not op2
|
||||
AstNode* rhsp() const { return op1p(); } // op1 = Assign from
|
||||
AstNode* lhsp() const { return op2p(); } // op2 = Assign to
|
||||
// op3 = Timing controls (delays, event controls)
|
||||
AstNode* timingControlp() const { return op3p(); }
|
||||
void addTimingControlp(AstNode* const np) { addNOp3p(np); }
|
||||
void rhsp(AstNode* np) { setOp1p(np); }
|
||||
void lhsp(AstNode* np) { setOp2p(np); }
|
||||
virtual bool hasDType() const override { return true; }
|
||||
|
|
|
|||
|
|
@ -1309,7 +1309,8 @@ void AstClassPackage::cloneRelink() {
|
|||
}
|
||||
void AstClass::insertCache(AstNode* nodep) {
|
||||
const bool doit = (VN_IS(nodep, Var) || VN_IS(nodep, EnumItemRef)
|
||||
|| (VN_IS(nodep, NodeFTask) && !VN_AS(nodep, NodeFTask)->isExternProto()));
|
||||
|| (VN_IS(nodep, NodeFTask) && !VN_AS(nodep, NodeFTask)->isExternProto())
|
||||
|| VN_IS(nodep, CFunc));
|
||||
if (doit) {
|
||||
if (m_members.find(nodep->name()) != m_members.end()) {
|
||||
nodep->v3error("Duplicate declaration of member name: " << nodep->prettyNameQ());
|
||||
|
|
@ -1320,7 +1321,14 @@ void AstClass::insertCache(AstNode* nodep) {
|
|||
}
|
||||
void AstClass::repairCache() {
|
||||
clearCache();
|
||||
for (AstNode* itemp = membersp(); itemp; itemp = itemp->nextp()) { insertCache(itemp); }
|
||||
for (auto* itemp = membersp(); itemp; itemp = itemp->nextp()) {
|
||||
if (const auto* const scopep = VN_CAST(itemp, Scope)) {
|
||||
for (auto* itemp = scopep->blocksp(); itemp; itemp = itemp->nextp())
|
||||
insertCache(itemp);
|
||||
} else {
|
||||
insertCache(itemp);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool AstClass::isClassExtendedFrom(const AstClass* refClassp, const AstClass* baseClassp) {
|
||||
// TAIL RECURSIVE
|
||||
|
|
|
|||
|
|
@ -2142,6 +2142,9 @@ public:
|
|||
virtual AstNodeDType* getChildDTypep() const override { return childDTypep(); }
|
||||
// op1 = Range of variable
|
||||
AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); }
|
||||
// op2 = Net delay
|
||||
AstNode* delayp() const { return op2p(); }
|
||||
void delayp(AstNode* const nodep) { setNOp2p(nodep); }
|
||||
AstNodeDType* dtypeSkipRefp() const { return subDTypep()->skipRefp(); }
|
||||
// (Slow) recurse down to find basic data type (Note don't need virtual -
|
||||
// AstVar isn't a NodeDType)
|
||||
|
|
@ -3481,8 +3484,8 @@ public:
|
|||
|
||||
class AstAssign final : public AstNodeAssign {
|
||||
public:
|
||||
AstAssign(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
|
||||
: ASTGEN_SUPER_Assign(fl, lhsp, rhsp) {
|
||||
AstAssign(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr)
|
||||
: ASTGEN_SUPER_Assign(fl, lhsp, rhsp, timingControlp) {
|
||||
dtypeFrom(lhsp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(Assign)
|
||||
|
|
@ -3507,8 +3510,8 @@ public:
|
|||
|
||||
class AstAssignDly final : public AstNodeAssign {
|
||||
public:
|
||||
AstAssignDly(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
|
||||
: ASTGEN_SUPER_AssignDly(fl, lhsp, rhsp) {}
|
||||
AstAssignDly(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr)
|
||||
: ASTGEN_SUPER_AssignDly(fl, lhsp, rhsp, timingControlp) {}
|
||||
ASTNODE_NODE_FUNCS(AssignDly)
|
||||
virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
|
||||
return new AstAssignDly(this->fileline(), lhsp, rhsp);
|
||||
|
|
@ -3817,15 +3820,18 @@ public:
|
|||
class AstDelay final : public AstNodeStmt {
|
||||
// Delay statement
|
||||
public:
|
||||
AstDelay(FileLine* fl, AstNode* lhsp)
|
||||
AstDelay(FileLine* fl, AstNode* lhsp, AstNode* stmtsp)
|
||||
: ASTGEN_SUPER_Delay(fl) {
|
||||
setOp1p(lhsp);
|
||||
setNOp2p(stmtsp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(Delay)
|
||||
virtual bool same(const AstNode* samep) const override { return true; }
|
||||
//
|
||||
AstNode* lhsp() const { return op1p(); } // op2 = Statements to evaluate
|
||||
AstNode* lhsp() const { return op1p(); } // op1 = delay value
|
||||
void lhsp(AstNode* nodep) { setOp1p(nodep); }
|
||||
void stmtsp(AstNode* nodep) { setOp2p(nodep); } // op2 = statements under delay
|
||||
AstNode* stmtsp() const { return op2p(); }
|
||||
};
|
||||
|
||||
class AstGenCase final : public AstNodeCase {
|
||||
|
|
@ -5243,15 +5249,15 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class AstTimingControl final : public AstNodeStmt {
|
||||
class AstEventControl final : public AstNodeStmt {
|
||||
// Parents: stmtlist
|
||||
public:
|
||||
AstTimingControl(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp)
|
||||
: ASTGEN_SUPER_TimingControl(fl) {
|
||||
AstEventControl(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp)
|
||||
: ASTGEN_SUPER_EventControl(fl) {
|
||||
setNOp1p(sensesp);
|
||||
setNOp2p(stmtsp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(TimingControl)
|
||||
ASTNODE_NODE_FUNCS(EventControl)
|
||||
virtual string verilogKwd() const override { return "@(%l) %r"; }
|
||||
virtual bool isGateOptimizable() const override { return false; }
|
||||
virtual bool isPredictOptimizable() const override { return false; }
|
||||
|
|
@ -8532,6 +8538,7 @@ public:
|
|||
AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); }
|
||||
void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); }
|
||||
AstNode* itemsp() const { return op2p(); } // op2 = AstPatReplicate, AstPatMember, etc
|
||||
void addItemsp(AstNode* nodep) { addOp2p(nodep); }
|
||||
};
|
||||
class AstPatMember final : public AstNodeMath {
|
||||
// Verilog '{a} or '{a{b}}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ public:
|
|||
}
|
||||
|
||||
// Get a reference to the user data
|
||||
T_Data& operator()(const T_Node* nodep) {
|
||||
T_Data& operator()(const T_Node* nodep) const {
|
||||
T_Data* const userp = getUserp(nodep);
|
||||
UASSERT_OBJ(userp, nodep, "Missing User data on const AstNode");
|
||||
return *userp;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,33 @@ private:
|
|||
return a + "__DOT__" + b;
|
||||
}
|
||||
|
||||
void dotNames(const AstNodeBlock* const nodep, const char* const blockName) {
|
||||
UINFO(8, "nname " << m_namedScope << endl);
|
||||
if (nodep->name() != "") { // Else unneeded unnamed block
|
||||
// Create data for dotted variable resolution
|
||||
string dottedname = nodep->name() + "__DOT__"; // So always found
|
||||
string::size_type pos;
|
||||
while ((pos = dottedname.find("__DOT__")) != string::npos) {
|
||||
const string ident = dottedname.substr(0, pos);
|
||||
dottedname = dottedname.substr(pos + strlen("__DOT__"));
|
||||
if (nodep->name() != "") {
|
||||
m_displayScope = dot(m_displayScope, ident);
|
||||
m_namedScope = dot(m_namedScope, ident);
|
||||
}
|
||||
m_unnamedScope = dot(m_unnamedScope, ident);
|
||||
// Create CellInline for dotted var resolution
|
||||
if (!m_ftaskp) {
|
||||
AstCellInline* const inlinep = new AstCellInline(
|
||||
nodep->fileline(), m_unnamedScope, blockName, m_modp->timeunit());
|
||||
m_modp->addInlinesp(inlinep); // Must be parsed before any AstCells
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remap var names and replace lower Begins
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
}
|
||||
|
||||
void liftNode(AstNode* nodep) {
|
||||
nodep->unlinkFrBack();
|
||||
if (m_ftaskp) {
|
||||
|
|
@ -141,30 +168,8 @@ private:
|
|||
VL_RESTORER(m_namedScope);
|
||||
VL_RESTORER(m_unnamedScope);
|
||||
{
|
||||
UINFO(8, "nname " << m_namedScope << endl);
|
||||
if (nodep->name() != "") { // Else unneeded unnamed block
|
||||
// Create data for dotted variable resolution
|
||||
string dottedname = nodep->name() + "__DOT__"; // So always found
|
||||
string::size_type pos;
|
||||
while ((pos = dottedname.find("__DOT__")) != string::npos) {
|
||||
const string ident = dottedname.substr(0, pos);
|
||||
dottedname = dottedname.substr(pos + strlen("__DOT__"));
|
||||
if (nodep->name() != "") {
|
||||
m_displayScope = dot(m_displayScope, ident);
|
||||
m_namedScope = dot(m_namedScope, ident);
|
||||
}
|
||||
m_unnamedScope = dot(m_unnamedScope, ident);
|
||||
// Create CellInline for dotted var resolution
|
||||
if (!m_ftaskp) {
|
||||
AstCellInline* const inlinep = new AstCellInline(
|
||||
nodep->fileline(), m_unnamedScope, "__BEGIN__", m_modp->timeunit());
|
||||
m_modp->addInlinesp(inlinep); // Must be parsed before any AstCells
|
||||
}
|
||||
}
|
||||
}
|
||||
dotNames(nodep, "__BEGIN__");
|
||||
|
||||
// Remap var names and replace lower Begins
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
UASSERT_OBJ(!nodep->genforp(), nodep, "GENFORs should have been expanded earlier");
|
||||
|
||||
// Cleanup
|
||||
|
|
|
|||
|
|
@ -496,7 +496,7 @@ private:
|
|||
V3Case::caseLint(nodep);
|
||||
iterateChildren(nodep);
|
||||
if (debug() >= 9) nodep->dumpTree(cout, " case_old: ");
|
||||
if (isCaseTreeFast(nodep) && v3Global.opt.oCase()) {
|
||||
if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) {
|
||||
// It's a simple priority encoder or complete statement
|
||||
// we can make a tree of statements to avoid extra comparisons
|
||||
++m_statCaseFast;
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ private:
|
|||
m_prefix = nodep->name() + "__02e"; // .
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
nodep->repairCache();
|
||||
}
|
||||
virtual void visit(AstNodeModule* nodep) override {
|
||||
// Visit for NodeModules that are not AstClass (AstClass is-a AstNodeModule)
|
||||
|
|
|
|||
133
src/V3Config.cpp
133
src/V3Config.cpp
|
|
@ -44,13 +44,8 @@ public:
|
|||
|
||||
/// Update into maps from other
|
||||
void update(const V3ConfigWildcardResolver& other) {
|
||||
typename Map::const_iterator it;
|
||||
for (it = other.m_mapResolved.begin(); it != other.m_mapResolved.end(); ++it) {
|
||||
m_mapResolved[it->first].update(it->second);
|
||||
}
|
||||
for (it = other.m_mapWildcard.begin(); it != other.m_mapWildcard.end(); ++it) {
|
||||
m_mapWildcard[it->first].update(it->second);
|
||||
}
|
||||
for (const auto& itr : other.m_mapResolved) m_mapResolved[itr.first].update(itr.second);
|
||||
for (const auto& itr : other.m_mapWildcard) m_mapWildcard[itr.first].update(itr.second);
|
||||
}
|
||||
|
||||
// Access and create a (wildcard) entity
|
||||
|
|
@ -68,12 +63,12 @@ public:
|
|||
// Cannot be resolved, create if matched
|
||||
|
||||
// Update this entity with all matches in the wildcards
|
||||
for (it = m_mapWildcard.begin(); it != m_mapWildcard.end(); ++it) {
|
||||
if (VString::wildmatch(name, it->first)) {
|
||||
for (const auto& wildent : m_mapWildcard) {
|
||||
if (VString::wildmatch(name, wildent.first)) {
|
||||
if (!newp) {
|
||||
newp = &m_mapResolved[name]; // Emplace and get pointer
|
||||
}
|
||||
newp->update(it->second);
|
||||
newp->update(wildent.second);
|
||||
}
|
||||
}
|
||||
return newp;
|
||||
|
|
@ -198,8 +193,8 @@ public:
|
|||
AstNode* const nodep = new AstPragma(modp->fileline(), type);
|
||||
modp->addStmtp(nodep);
|
||||
}
|
||||
for (auto it = m_modPragmas.cbegin(); it != m_modPragmas.cend(); ++it) {
|
||||
AstNode* const nodep = new AstPragma(modp->fileline(), *it);
|
||||
for (const auto& itr : m_modPragmas) {
|
||||
AstNode* const nodep = new AstPragma{modp->fileline(), itr};
|
||||
modp->addStmtp(nodep);
|
||||
}
|
||||
}
|
||||
|
|
@ -344,25 +339,124 @@ public:
|
|||
|
||||
using V3ConfigFileResolver = V3ConfigWildcardResolver<V3ConfigFile>;
|
||||
|
||||
//######################################################################
|
||||
// ScopeTrace tracking
|
||||
|
||||
class V3ConfigScopeTraceEntry final {
|
||||
public:
|
||||
const string m_scope; // Scope or regexp to match
|
||||
const bool m_on = false; // True to enable message
|
||||
int m_levels = 0; // # levels, 0 = all, 1 = only this, ...
|
||||
// CONSTRUCTORS
|
||||
V3ConfigScopeTraceEntry(const string& scope, bool on, int levels)
|
||||
: m_scope{scope}
|
||||
, m_on{on}
|
||||
, m_levels{levels} {}
|
||||
~V3ConfigScopeTraceEntry() = default;
|
||||
bool operator<(const V3ConfigScopeTraceEntry& other) const {
|
||||
if (m_on < other.m_on) return true;
|
||||
if (m_on > other.m_on) return false;
|
||||
if (m_levels < other.m_levels) return true;
|
||||
if (m_levels > other.m_levels) return false;
|
||||
return m_scope < other.m_scope;
|
||||
}
|
||||
};
|
||||
|
||||
// Tracks what matches are known to hit against V3ConfigScopeTraceEntries
|
||||
class V3ConfigScopeTraceEntryMatch final {
|
||||
public:
|
||||
const V3ConfigScopeTraceEntry* m_entryp;
|
||||
const string m_scopepart;
|
||||
V3ConfigScopeTraceEntryMatch(const V3ConfigScopeTraceEntry* entryp, const string& scopepart)
|
||||
: m_entryp{entryp}
|
||||
, m_scopepart{scopepart} {}
|
||||
bool operator<(const V3ConfigScopeTraceEntryMatch& other) const {
|
||||
if (m_entryp < other.m_entryp) return true;
|
||||
if (m_entryp > other.m_entryp) return false;
|
||||
return m_scopepart < other.m_scopepart;
|
||||
}
|
||||
};
|
||||
|
||||
class V3ConfigScopeTraceResolver final {
|
||||
std::vector<V3ConfigScopeTraceEntry> m_entries; // User specified on/offs and levels
|
||||
std::map<V3ConfigScopeTraceEntryMatch, bool> m_matchCache; // Matching entries for speed
|
||||
|
||||
public:
|
||||
void addScopeTraceOn(bool on, const string& scope, int levels) {
|
||||
UINFO(9, "addScopeTraceOn " << on << " '" << scope << "' "
|
||||
<< " levels=" << levels << endl);
|
||||
m_entries.emplace_back(V3ConfigScopeTraceEntry{scope, on, levels});
|
||||
m_matchCache.clear();
|
||||
}
|
||||
|
||||
bool getEntryMatch(const V3ConfigScopeTraceEntry* entp, const string& scopepart) {
|
||||
// Return if a entry matches the scopepart, with memoization
|
||||
const auto& key = V3ConfigScopeTraceEntryMatch{entp, scopepart};
|
||||
const auto& it = m_matchCache.find(key);
|
||||
if (it != m_matchCache.end()) return it->second; // Cached
|
||||
const bool matched = VString::wildmatch(scopepart, entp->m_scope);
|
||||
m_matchCache.emplace(key, matched);
|
||||
return matched;
|
||||
}
|
||||
|
||||
bool getScopeTraceOn(const string& scope) {
|
||||
// Apply in the order the user provided them, so they can choose on/off preferencing
|
||||
int maxLevel = 1;
|
||||
for (const auto& ch : scope) {
|
||||
if (ch == '.') ++maxLevel;
|
||||
}
|
||||
UINFO(9, "getScopeTraceOn " << scope << " maxLevel=" << maxLevel << endl);
|
||||
|
||||
bool enabled = true;
|
||||
for (const auto& ent : m_entries) {
|
||||
// We apply shortest match first for each rule component
|
||||
// (Otherwise the levels would be useless as "--scope top* --levels 1" would
|
||||
// always match at every scopepart, and we wound't know how to count levels)
|
||||
int partLevel = 1;
|
||||
for (string::size_type partEnd = 0; true;) {
|
||||
partEnd = scope.find('.', partEnd + 1);
|
||||
if (partEnd == string::npos) partEnd = scope.length();
|
||||
const std::string scopepart = scope.substr(0, partEnd);
|
||||
if (getEntryMatch(&ent, scopepart)) {
|
||||
const bool levelMatch
|
||||
= !ent.m_levels || (ent.m_levels >= maxLevel - partLevel);
|
||||
if (levelMatch) enabled = ent.m_on;
|
||||
UINFO(9, "getScopeTraceOn-part " << scope << " enabled=" << enabled
|
||||
<< " @ lev=" << partLevel
|
||||
<< (levelMatch ? "[match]" : "[miss]")
|
||||
<< " from scopepart=" << scopepart << endl);
|
||||
break;
|
||||
}
|
||||
if (partEnd == scope.length()) break;
|
||||
++partLevel;
|
||||
}
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Resolve modules and files in the design
|
||||
|
||||
class V3ConfigResolver final {
|
||||
V3ConfigModuleResolver m_modules; // Access to module names (with wildcards)
|
||||
V3ConfigFileResolver m_files; // Access to file names (with wildcards)
|
||||
V3ConfigScopeTraceResolver m_scopeTraces; // Regexp to trace enables
|
||||
std::unordered_map<string, std::unordered_map<string, uint64_t>>
|
||||
m_profileData; // Access to profile_data records
|
||||
FileLine* m_profileFileLine = nullptr;
|
||||
|
||||
static V3ConfigResolver s_singleton; // Singleton (not via local static, as that's slow)
|
||||
V3ConfigResolver() = default;
|
||||
~V3ConfigResolver() = default;
|
||||
|
||||
public:
|
||||
static V3ConfigResolver& s() { return s_singleton; }
|
||||
|
||||
static V3ConfigResolver& s() {
|
||||
static V3ConfigResolver s_singleton;
|
||||
return s_singleton;
|
||||
}
|
||||
V3ConfigModuleResolver& modules() { return m_modules; }
|
||||
V3ConfigFileResolver& files() { return m_files; }
|
||||
V3ConfigScopeTraceResolver& scopeTraces() { return m_scopeTraces; }
|
||||
|
||||
void addProfileData(FileLine* fl, const string& model, const string& key, uint64_t cost) {
|
||||
if (!m_profileFileLine) m_profileFileLine = fl;
|
||||
|
|
@ -379,8 +473,6 @@ public:
|
|||
FileLine* getProfileDataFileLine() const { return m_profileFileLine; } // Maybe null
|
||||
};
|
||||
|
||||
V3ConfigResolver V3ConfigResolver::s_singleton;
|
||||
|
||||
//######################################################################
|
||||
// V3Config
|
||||
|
||||
|
|
@ -434,6 +526,10 @@ void V3Config::addProfileData(FileLine* fl, const string& model, const string& k
|
|||
V3ConfigResolver::s().addProfileData(fl, model, key, cost);
|
||||
}
|
||||
|
||||
void V3Config::addScopeTraceOn(bool on, const string& scope, int levels) {
|
||||
V3ConfigResolver::s().scopeTraces().addScopeTraceOn(on, scope, levels);
|
||||
}
|
||||
|
||||
void V3Config::addVarAttr(FileLine* fl, const string& module, const string& ftask,
|
||||
const string& var, VAttrType attr, AstSenTree* sensep) {
|
||||
// Semantics: sensep only if public_flat_rw
|
||||
|
|
@ -540,6 +636,9 @@ uint64_t V3Config::getProfileData(const string& model, const string& key) {
|
|||
FileLine* V3Config::getProfileDataFileLine() {
|
||||
return V3ConfigResolver::s().getProfileDataFileLine();
|
||||
}
|
||||
bool V3Config::getScopeTraceOn(const string& scope) {
|
||||
return V3ConfigResolver::s().scopeTraces().getScopeTraceOn(scope);
|
||||
}
|
||||
|
||||
bool V3Config::waive(FileLine* filelinep, V3ErrorCode code, const string& message) {
|
||||
V3ConfigFile* filep = V3ConfigResolver::s().files().resolve(filelinep->filename());
|
||||
|
|
|
|||
|
|
@ -37,19 +37,21 @@ public:
|
|||
static void addModulePragma(const string& module, VPragmaType pragma);
|
||||
static void addProfileData(FileLine* fl, const string& model, const string& key,
|
||||
uint64_t cost);
|
||||
static void addWaiver(V3ErrorCode code, const string& filename, const string& message);
|
||||
static void addScopeTraceOn(bool on, const string& scope, int levels);
|
||||
static void addVarAttr(FileLine* fl, const string& module, const string& ftask,
|
||||
const string& signal, VAttrType type, AstSenTree* nodep);
|
||||
static void addWaiver(V3ErrorCode code, const string& filename, const string& message);
|
||||
|
||||
static void applyCase(AstCase* nodep);
|
||||
static void applyCoverageBlock(AstNodeModule* modulep, AstBegin* nodep);
|
||||
static void applyFTask(AstNodeModule* modulep, AstNodeFTask* ftaskp);
|
||||
static void applyIgnores(FileLine* filelinep);
|
||||
static void applyModule(AstNodeModule* modulep);
|
||||
static void applyFTask(AstNodeModule* modulep, AstNodeFTask* ftaskp);
|
||||
static void applyVarAttr(AstNodeModule* modulep, AstNodeFTask* ftaskp, AstVar* varp);
|
||||
|
||||
static uint64_t getProfileData(const string& model, const string& key);
|
||||
static FileLine* getProfileDataFileLine();
|
||||
static bool getScopeTraceOn(const string& scope);
|
||||
static bool waive(FileLine* filelinep, V3ErrorCode code, const string& message);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,15 @@ class ConstBitOpTreeVisitor final : public VNVisitor {
|
|||
BitPolarityEntry() = default;
|
||||
};
|
||||
|
||||
struct FrozenNodeInfo final { // Context when a frozen node is found
|
||||
bool m_polarity;
|
||||
int m_lsb;
|
||||
bool operator<(const FrozenNodeInfo& other) const {
|
||||
if (m_lsb != other.m_lsb) return m_lsb < other.m_lsb;
|
||||
return m_polarity < other.m_polarity;
|
||||
}
|
||||
};
|
||||
|
||||
class Restorer final { // Restore the original state unless disableRestore() is called
|
||||
ConstBitOpTreeVisitor& m_visitor;
|
||||
const size_t m_polaritiesSize;
|
||||
|
|
@ -299,7 +308,8 @@ class ConstBitOpTreeVisitor final : public VNVisitor {
|
|||
LeafInfo* m_leafp = nullptr; // AstConst or AstVarRef that currently looking for
|
||||
const AstNode* const m_rootp; // Root of this AST subtree
|
||||
|
||||
std::vector<AstNode*> m_frozenNodes; // Nodes that cannot be optimized
|
||||
std::vector<std::pair<AstNode*, FrozenNodeInfo>>
|
||||
m_frozenNodes; // Nodes that cannot be optimized
|
||||
std::vector<BitPolarityEntry> m_bitPolarities; // Polarity of bits found during iterate()
|
||||
std::vector<std::unique_ptr<VarInfo>> m_varInfos; // VarInfo for each variable, [0] is nullptr
|
||||
|
||||
|
|
@ -487,7 +497,7 @@ class ConstBitOpTreeVisitor final : public VNVisitor {
|
|||
restorer.restoreNow();
|
||||
// Reach past a cast then add to frozen nodes to be added to final reduction
|
||||
if (const AstCCast* const castp = VN_CAST(opp, CCast)) opp = castp->lhsp();
|
||||
m_frozenNodes.push_back(opp);
|
||||
m_frozenNodes.emplace_back(opp, FrozenNodeInfo{m_polarity, m_lsb});
|
||||
m_failed = origFailed;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -652,17 +662,21 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
std::map<FrozenNodeInfo, std::vector<AstNode*>> frozenNodes; // Group by FrozenNodeInfo
|
||||
// Check if frozen terms are clean or not
|
||||
for (AstNode* const termp : visitor.m_frozenNodes) {
|
||||
for (const auto& frozenInfo : visitor.m_frozenNodes) {
|
||||
AstNode* const termp = frozenInfo.first;
|
||||
// Comparison operators are clean
|
||||
if (VN_IS(termp, Eq) || VN_IS(termp, Neq) || VN_IS(termp, Lt) || VN_IS(termp, Lte)
|
||||
|| VN_IS(termp, Gt) || VN_IS(termp, Gte)) {
|
||||
if ((VN_IS(termp, Eq) || VN_IS(termp, Neq) || VN_IS(termp, Lt) || VN_IS(termp, Lte)
|
||||
|| VN_IS(termp, Gt) || VN_IS(termp, Gte))
|
||||
&& frozenInfo.second.m_lsb == 0) {
|
||||
hasCleanTerm = true;
|
||||
} else {
|
||||
// Otherwise, conservatively assume the frozen term is dirty
|
||||
hasDirtyTerm = true;
|
||||
UINFO(9, "Dirty frozen term: " << termp << endl);
|
||||
}
|
||||
frozenNodes[frozenInfo.second].push_back(termp);
|
||||
}
|
||||
|
||||
// Figure out if a final negation is required
|
||||
|
|
@ -672,7 +686,12 @@ public:
|
|||
const bool needsCleaning = visitor.isAndTree() ? !hasCleanTerm : hasDirtyTerm;
|
||||
|
||||
// Add size of reduction tree to op count
|
||||
resultOps += termps.size() + visitor.m_frozenNodes.size() - 1;
|
||||
resultOps += termps.size() - 1;
|
||||
for (const auto& lsbAndNodes : frozenNodes) {
|
||||
if (lsbAndNodes.first.m_lsb > 0) ++resultOps; // Needs AstShiftR
|
||||
if (!lsbAndNodes.first.m_polarity) ++resultOps; // Needs AstNot
|
||||
resultOps += lsbAndNodes.second.size();
|
||||
}
|
||||
// Add final polarity flip in Xor tree
|
||||
if (needsFlip) ++resultOps;
|
||||
// Add final cleaning AND
|
||||
|
|
@ -681,7 +700,10 @@ public:
|
|||
if (debug() >= 9) { // LCOV_EXCL_START
|
||||
cout << "Bitop tree considered: " << endl;
|
||||
for (AstNode* const termp : termps) termp->dumpTree("Reduced term: ");
|
||||
for (AstNode* const termp : visitor.m_frozenNodes) termp->dumpTree("Frozen term: ");
|
||||
for (const std::pair<AstNode*, FrozenNodeInfo>& termp : visitor.m_frozenNodes)
|
||||
termp.first->dumpTree("Frozen term with lsb " + std::to_string(termp.second.m_lsb)
|
||||
+ " polarity " + std::to_string(termp.second.m_polarity)
|
||||
+ ": ");
|
||||
cout << "Needs flipping: " << needsFlip << endl;
|
||||
cout << "Needs cleaning: " << needsCleaning << endl;
|
||||
cout << "Size: " << resultOps << " input size: " << visitor.m_ops << endl;
|
||||
|
|
@ -724,8 +746,25 @@ public:
|
|||
resultp = reduce(resultp, termp);
|
||||
}
|
||||
// Add any frozen terms to the reduction
|
||||
for (AstNode* const frozenp : visitor.m_frozenNodes) {
|
||||
resultp = reduce(resultp, frozenp->unlinkFrBack());
|
||||
for (auto&& nodes : frozenNodes) {
|
||||
// nodes.second has same lsb and polarity
|
||||
AstNode* termp = nullptr;
|
||||
for (AstNode* const itemp : nodes.second) {
|
||||
termp = reduce(termp, itemp->unlinkFrBack());
|
||||
}
|
||||
if (nodes.first.m_lsb > 0) { // LSB is not 0, so shiftR
|
||||
AstNodeDType* const dtypep = termp->dtypep();
|
||||
termp = new AstShiftR{termp->fileline(), termp,
|
||||
new AstConst(termp->fileline(), AstConst::WidthedValue{},
|
||||
termp->width(), nodes.first.m_lsb)};
|
||||
termp->dtypep(dtypep);
|
||||
}
|
||||
if (!nodes.first.m_polarity) { // Polarity is inverted, so append Not
|
||||
AstNodeDType* const dtypep = termp->dtypep();
|
||||
termp = new AstNot{termp->fileline(), termp};
|
||||
termp->dtypep(dtypep);
|
||||
}
|
||||
resultp = reduce(resultp, termp);
|
||||
}
|
||||
|
||||
// Set width of masks to expected result width. This is required to prevent later removal
|
||||
|
|
@ -769,6 +808,9 @@ public:
|
|||
|
||||
class ConstVisitor final : public VNVisitor {
|
||||
private:
|
||||
// CONSTANTS
|
||||
static constexpr unsigned CONCAT_MERGABLE_MAX_DEPTH = 10; // Limit alg recursion
|
||||
|
||||
// NODE STATE
|
||||
// ** only when m_warn/m_doExpensive is set. If state is needed other times,
|
||||
// ** must track down everywhere V3Const is called and make sure no overlaps.
|
||||
|
|
@ -1048,7 +1090,7 @@ private:
|
|||
|
||||
bool matchBitOpTree(AstNode* nodep) {
|
||||
if (nodep->widthMin() != 1) return false;
|
||||
if (!v3Global.opt.oConstBitOpTree()) return false;
|
||||
if (!v3Global.opt.fConstBitOpTree()) return false;
|
||||
|
||||
string debugPrefix;
|
||||
if (debug() >= 9) { // LCOV_EXCL_START
|
||||
|
|
@ -1370,7 +1412,7 @@ private:
|
|||
return (VN_IS(nodep, And) || VN_IS(nodep, Or) || VN_IS(nodep, Xor));
|
||||
}
|
||||
bool ifAdjacentSel(const AstSel* lhsp, const AstSel* rhsp) {
|
||||
if (!v3Global.opt.oAssemble()) return false; // opt disabled
|
||||
if (!v3Global.opt.fAssemble()) return false; // opt disabled
|
||||
if (!lhsp || !rhsp) return false;
|
||||
const AstNode* const lfromp = lhsp->fromp();
|
||||
const AstNode* const rfromp = rhsp->fromp();
|
||||
|
|
@ -1385,7 +1427,7 @@ private:
|
|||
}
|
||||
bool ifMergeAdjacent(AstNode* lhsp, AstNode* rhsp) {
|
||||
// called by concatmergeable to determine if {lhsp, rhsp} make sense
|
||||
if (!v3Global.opt.oAssemble()) return false; // opt disabled
|
||||
if (!v3Global.opt.fAssemble()) return false; // opt disabled
|
||||
// two same varref
|
||||
if (operandsSame(lhsp, rhsp)) return true;
|
||||
const AstSel* lselp = VN_CAST(lhsp, Sel);
|
||||
|
|
@ -1420,11 +1462,12 @@ private:
|
|||
if (rend == rfromp->width() && lstart->toSInt() == 0) return true;
|
||||
return false;
|
||||
}
|
||||
bool concatMergeable(const AstNode* lhsp, const AstNode* rhsp) {
|
||||
bool concatMergeable(const AstNode* lhsp, const AstNode* rhsp, unsigned depth) {
|
||||
// determine if {a OP b, c OP d} => {a, c} OP {b, d} is advantageous
|
||||
if (!v3Global.opt.oAssemble()) return false; // opt disabled
|
||||
if (!v3Global.opt.fAssemble()) return false; // opt disabled
|
||||
if (lhsp->type() != rhsp->type()) return false;
|
||||
if (!ifConcatMergeableBiop(lhsp)) return false;
|
||||
if (depth > CONCAT_MERGABLE_MAX_DEPTH) return false; // As worse case O(n^2) algorithm
|
||||
|
||||
const AstNodeBiop* const lp = VN_CAST(lhsp, NodeBiop);
|
||||
const AstNodeBiop* const rp = VN_CAST(rhsp, NodeBiop);
|
||||
|
|
@ -1434,11 +1477,12 @@ private:
|
|||
const bool rad = ifMergeAdjacent(lp->rhsp(), rp->rhsp());
|
||||
if (lad && rad) return true;
|
||||
// {a[] & b[]&c[], a[] & b[]&c[]}
|
||||
if (lad && concatMergeable(lp->rhsp(), rp->rhsp())) return true;
|
||||
if (lad && concatMergeable(lp->rhsp(), rp->rhsp(), depth + 1)) return true;
|
||||
// {a[]&b[] & c[], a[]&b[] & c[]}
|
||||
if (rad && concatMergeable(lp->lhsp(), rp->lhsp())) return true;
|
||||
if (rad && concatMergeable(lp->lhsp(), rp->lhsp(), depth + 1)) return true;
|
||||
// {(a[]&b[])&(c[]&d[]), (a[]&b[])&(c[]&d[])}
|
||||
if (concatMergeable(lp->lhsp(), rp->lhsp()) && concatMergeable(lp->rhsp(), rp->rhsp())) {
|
||||
if (concatMergeable(lp->lhsp(), rp->lhsp(), depth + 1)
|
||||
&& concatMergeable(lp->rhsp(), rp->rhsp(), depth + 1)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -1698,7 +1742,7 @@ private:
|
|||
AstNode* const lrp = lp->rhsp()->cloneTree(false);
|
||||
AstNode* const rlp = rp->lhsp()->cloneTree(false);
|
||||
AstNode* const rrp = rp->rhsp()->cloneTree(false);
|
||||
if (concatMergeable(lp, rp)) {
|
||||
if (concatMergeable(lp, rp, 0)) {
|
||||
AstConcat* const newlp = new AstConcat(rlp->fileline(), llp, rlp);
|
||||
AstConcat* const newrp = new AstConcat(rrp->fileline(), lrp, rrp);
|
||||
// use the lhs to replace the parent concat
|
||||
|
|
@ -2506,7 +2550,7 @@ private:
|
|||
if (nodep->access().isReadOnly()
|
||||
&& ((!m_params // Can reduce constant wires into equations
|
||||
&& m_doNConst
|
||||
&& v3Global.opt.oConst()
|
||||
&& v3Global.opt.fConst()
|
||||
// Default value, not a "known" constant for this usage
|
||||
&& !nodep->varp()->isClassMember()
|
||||
&& !(nodep->varp()->isFuncLocal() && nodep->varp()->isNonOutput())
|
||||
|
|
@ -3368,7 +3412,7 @@ private:
|
|||
TREEOPV("AstConcat{$lhsp.isZero, $rhsp}", "replaceExtend(nodep, nodep->rhsp())");
|
||||
// CONCAT(a[1],a[0]) -> a[1:0]
|
||||
TREEOPV("AstConcat{$lhsp.castSel, $rhsp.castSel, ifAdjacentSel(VN_AS($lhsp,,Sel),,VN_AS($rhsp,,Sel))}", "replaceConcatSel(nodep)");
|
||||
TREEOPV("AstConcat{ifConcatMergeableBiop($lhsp), concatMergeable($lhsp,,$rhsp)}", "replaceConcatMerge(nodep)");
|
||||
TREEOPV("AstConcat{ifConcatMergeableBiop($lhsp), concatMergeable($lhsp,,$rhsp,,0)}", "replaceConcatMerge(nodep)");
|
||||
// Common two-level operations that can be simplified
|
||||
TREEOP ("AstAnd {$lhsp.castConst,matchAndCond(nodep)}", "DONE");
|
||||
TREEOP ("AstAnd {$lhsp.castConst, $rhsp.castOr, matchMaskedOr(nodep)}", "DONE");
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ private:
|
|||
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_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
|
||||
VDouble0 m_statSharedSet; // Statistic tracking
|
||||
|
|
@ -105,6 +106,7 @@ private:
|
|||
void markVarUsage(AstNodeVarRef* nodep, bool blocking) {
|
||||
// Ignore if warning is disabled on this reference (used by V3Force).
|
||||
if (nodep->fileline()->warnIsOff(V3ErrorCode::BLKANDNBLK)) return;
|
||||
if (m_ignoreBlkAndNBlk) return;
|
||||
if (blocking) nodep->user5(true);
|
||||
AstVarScope* const vscp = nodep->varScopep();
|
||||
// UINFO(4, " MVU " << blocking << " " << nodep << endl);
|
||||
|
|
@ -485,6 +487,13 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
virtual void visit(AstNodeReadWriteMem* nodep) override {
|
||||
VL_RESTORER(m_ignoreBlkAndNBlk);
|
||||
m_ignoreBlkAndNBlk = true; // $readmem/$writemem often used in mem models
|
||||
// so we will suppress BLKANDNBLK warnings
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
virtual void visit(AstNodeFor* nodep) override { // LCOV_EXCL_LINE
|
||||
nodep->v3fatalSrc(
|
||||
"For statements should have been converted to while statements in V3Begin");
|
||||
|
|
|
|||
|
|
@ -308,7 +308,8 @@ void EmitCFunc::displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const
|
|||
}
|
||||
emitDispState.pushFormat(pfmt);
|
||||
if (!ignore) {
|
||||
if (argp->dtypep()->basicp()->keyword() == VBasicDTypeKwd::STRING) {
|
||||
if (argp->dtypep()->basicp()
|
||||
&& argp->dtypep()->basicp()->keyword() == VBasicDTypeKwd::STRING) {
|
||||
// string in SystemVerilog is std::string in C++ which is not POD
|
||||
emitDispState.pushArg(' ', nullptr, "-1");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -751,26 +751,26 @@ class EmitCTrace final : EmitCFunc {
|
|||
const string func = nodep->full() ? "full" : "chg";
|
||||
bool emitWidth = true;
|
||||
if (nodep->dtypep()->basicp()->isDouble()) {
|
||||
puts("tracep->" + func + "Double");
|
||||
puts("bufp->" + func + "Double");
|
||||
emitWidth = false;
|
||||
} else if (nodep->isWide() || emitTraceIsScBv(nodep) || emitTraceIsScBigUint(nodep)) {
|
||||
puts("tracep->" + func + "WData");
|
||||
puts("bufp->" + func + "WData");
|
||||
} else if (nodep->isQuad()) {
|
||||
puts("tracep->" + func + "QData");
|
||||
puts("bufp->" + func + "QData");
|
||||
} else if (nodep->declp()->widthMin() > 16) {
|
||||
puts("tracep->" + func + "IData");
|
||||
puts("bufp->" + func + "IData");
|
||||
} else if (nodep->declp()->widthMin() > 8) {
|
||||
puts("tracep->" + func + "SData");
|
||||
puts("bufp->" + func + "SData");
|
||||
} else if (nodep->declp()->widthMin() > 1) {
|
||||
puts("tracep->" + func + "CData");
|
||||
puts("bufp->" + func + "CData");
|
||||
} else {
|
||||
puts("tracep->" + func + "Bit");
|
||||
puts("bufp->" + func + "Bit");
|
||||
emitWidth = false;
|
||||
}
|
||||
|
||||
const uint32_t offset = (arrayindex < 0) ? 0 : (arrayindex * nodep->declp()->widthWords());
|
||||
const uint32_t code = nodep->declp()->code() + offset;
|
||||
puts(v3Global.opt.trueTraceThreads() && !nodep->full() ? "(base+" : "(oldp+");
|
||||
puts(v3Global.opt.useTraceOffload() && !nodep->full() ? "(base+" : "(oldp+");
|
||||
puts(cvtToStr(code - nodep->baseCode()));
|
||||
puts(",");
|
||||
emitTraceValue(nodep, arrayindex);
|
||||
|
|
|
|||
|
|
@ -113,20 +113,18 @@ class CMakeEmitter final {
|
|||
cmake_set_raw(*of, name + "_COVERAGE", v3Global.opt.coverage() ? "1" : "0");
|
||||
*of << "# Threaded output mode? 0/1/N threads (from --threads)\n";
|
||||
cmake_set_raw(*of, name + "_THREADS", cvtToStr(v3Global.opt.threads()));
|
||||
*of << "# Threaded tracing output mode? 0/1/N threads (from --trace-threads)\n";
|
||||
cmake_set_raw(*of, name + "_TRACE_THREADS", cvtToStr(v3Global.opt.traceThreads()));
|
||||
*of << "# Threaded tracing output mode? 0/1/N threads (from --threads/--trace-threads)\n";
|
||||
cmake_set_raw(*of, name + "_TRACE_THREADS", cvtToStr(v3Global.opt.vmTraceThreads()));
|
||||
cmake_set_raw(*of, name + "_TRACE_FST_WRITER_THREAD",
|
||||
v3Global.opt.traceThreads() && v3Global.opt.traceFormat().fst() ? "1" : "0");
|
||||
*of << "# Struct output mode? 0/1 (from --trace-structs)\n";
|
||||
cmake_set_raw(*of, name + "_TRACE_STRUCTS", cvtToStr(v3Global.opt.traceStructs()));
|
||||
*of << "# VCD Tracing output mode? 0/1 (from --trace)\n";
|
||||
cmake_set_raw(*of, name + "_TRACE_VCD",
|
||||
(v3Global.opt.trace() && (v3Global.opt.traceFormat() == TraceFormat::VCD))
|
||||
? "1"
|
||||
: "0");
|
||||
*of << "# FST Tracing output mode? 0/1 (from --fst-trace)\n";
|
||||
(v3Global.opt.trace() && v3Global.opt.traceFormat().vcd()) ? "1" : "0");
|
||||
*of << "# FST Tracing output mode? 0/1 (from --trace-fst)\n";
|
||||
cmake_set_raw(*of, name + "_TRACE_FST",
|
||||
(v3Global.opt.trace() && (v3Global.opt.traceFormat() != TraceFormat::VCD))
|
||||
? "1"
|
||||
: "0");
|
||||
(v3Global.opt.trace() && v3Global.opt.traceFormat().fst()) ? "1" : "0");
|
||||
|
||||
*of << "\n### Sources...\n";
|
||||
std::vector<string> classes_fast;
|
||||
|
|
|
|||
|
|
@ -65,13 +65,18 @@ public:
|
|||
of.puts("VM_TRACE = ");
|
||||
of.puts(v3Global.opt.trace() ? "1" : "0");
|
||||
of.puts("\n");
|
||||
of.puts("# Tracing output mode in VCD format? 0/1 (from --trace)\n");
|
||||
of.puts("VM_TRACE_VCD = ");
|
||||
of.puts(v3Global.opt.trace() && v3Global.opt.traceFormat().vcd() ? "1" : "0");
|
||||
of.puts("\n");
|
||||
of.puts("# Tracing output mode in FST format? 0/1 (from --trace-fst)\n");
|
||||
of.puts("VM_TRACE_FST = ");
|
||||
of.puts(v3Global.opt.trace() && v3Global.opt.traceFormat().fst() ? "1" : "0");
|
||||
of.puts("\n");
|
||||
of.puts("# Tracing threaded output mode? 0/1/N threads (from --trace-thread)\n");
|
||||
of.puts(
|
||||
"# Tracing threaded output mode? 0/1/N threads (from --threads/--trace-thread)\n");
|
||||
of.puts("VM_TRACE_THREADS = ");
|
||||
of.puts(cvtToStr(v3Global.opt.trueTraceThreads()));
|
||||
of.puts(cvtToStr(v3Global.opt.vmTraceThreads()));
|
||||
of.puts("\n");
|
||||
of.puts("# Separate FST writer thread? 0/1 (from --trace-fst with --trace-thread > 0)\n");
|
||||
of.puts("VM_TRACE_FST_WRITER_THREAD = ");
|
||||
|
|
|
|||
|
|
@ -790,6 +790,10 @@ class EmitVPrefixedFormatter final : public V3OutFormatter {
|
|||
}
|
||||
}
|
||||
|
||||
virtual void putsOutput(const char* strg) override {
|
||||
for (const char* cp = strg; *cp; cp++) putcOutput(*cp);
|
||||
}
|
||||
|
||||
public:
|
||||
void prefixFl(FileLine* fl) { m_prefixFl = fl; }
|
||||
FileLine* prefixFl() const { return m_prefixFl; }
|
||||
|
|
|
|||
|
|
@ -700,6 +700,10 @@ int V3OutFormatter::endLevels(const char* strg) {
|
|||
}
|
||||
|
||||
void V3OutFormatter::puts(const char* strg) {
|
||||
if (!v3Global.opt.decoration()) {
|
||||
putsOutput(strg);
|
||||
return;
|
||||
}
|
||||
if (m_prependIndent && strg[0] != '\n') {
|
||||
putsNoTracking(indentSpaces(endLevels(strg)));
|
||||
m_prependIndent = false;
|
||||
|
|
@ -759,13 +763,8 @@ void V3OutFormatter::puts(const char* strg) {
|
|||
break;
|
||||
case '(':
|
||||
indentInc();
|
||||
if (v3Global.opt.decoration()) {
|
||||
// Line up continuation with open paren, plus one indent
|
||||
m_parenVec.push(m_column);
|
||||
} else {
|
||||
// Line up continuation with block+1
|
||||
m_parenVec.push(m_indentLevel * m_blockIndent);
|
||||
}
|
||||
// Line up continuation with open paren, plus one indent
|
||||
m_parenVec.push(m_column);
|
||||
break;
|
||||
case ')':
|
||||
if (!m_parenVec.empty()) m_parenVec.pop();
|
||||
|
|
@ -806,6 +805,7 @@ void V3OutFormatter::putBreakExpr() {
|
|||
|
||||
// Add a line break if too wide
|
||||
void V3OutFormatter::putBreak() {
|
||||
if (!v3Global.opt.decoration()) return;
|
||||
if (!m_nobreak) {
|
||||
// char s[1000]; sprintf(s, "{%d,%d}", m_column, m_parenVec.top()); putsNoTracking(s);
|
||||
if (exceededWidth()) {
|
||||
|
|
@ -824,11 +824,19 @@ void V3OutFormatter::putsQuoted(const string& strg) {
|
|||
putcNoTracking('"');
|
||||
}
|
||||
void V3OutFormatter::putsNoTracking(const string& strg) {
|
||||
if (!v3Global.opt.decoration()) {
|
||||
putsOutput(strg.c_str());
|
||||
return;
|
||||
}
|
||||
// Don't track {}'s, probably because it's a $display format string
|
||||
for (const char c : strg) putcNoTracking(c);
|
||||
}
|
||||
|
||||
void V3OutFormatter::putcNoTracking(char chr) {
|
||||
if (!v3Global.opt.decoration()) {
|
||||
putcOutput(chr);
|
||||
return;
|
||||
}
|
||||
switch (chr) {
|
||||
case '\n':
|
||||
m_lineno++;
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ public:
|
|||
|
||||
// CALLBACKS - MUST OVERRIDE
|
||||
virtual void putcOutput(char chr) = 0;
|
||||
virtual void putsOutput(const char* str) = 0;
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
|
|
@ -193,6 +194,7 @@ public:
|
|||
private:
|
||||
// CALLBACKS
|
||||
virtual void putcOutput(char chr) override { fputc(chr, m_fp); }
|
||||
virtual void putsOutput(const char* str) override { fputs(str, m_fp); }
|
||||
};
|
||||
|
||||
class V3OutCFile VL_NOT_FINAL : public V3OutFile {
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ FileLine::FileLine(FileLine::EmptySecret) {
|
|||
}
|
||||
|
||||
void FileLine::newContent() {
|
||||
m_contentp = new VFileContent;
|
||||
m_contentp = std::make_shared<VFileContent>();
|
||||
m_contentLineno = 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <sstream>
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
|
|
@ -97,7 +98,7 @@ class FileLine final {
|
|||
int m_lastColumn = 0; // `line corrected token's last column number
|
||||
int m_filenameno; // `line corrected filename number
|
||||
int m_contentLineno = 0; // Line number within source stream
|
||||
VFileContent* m_contentp = nullptr; // Source text contents line is within
|
||||
std::shared_ptr<VFileContent> m_contentp = nullptr; // Source text contents line is within
|
||||
FileLine* m_parent = nullptr; // Parent line that included this line
|
||||
std::bitset<V3ErrorCode::_ENUM_MAX> m_warnOn;
|
||||
bool m_waive = false; // Waive warning
|
||||
|
|
@ -180,7 +181,7 @@ public:
|
|||
int firstColumn() const { return m_firstColumn; }
|
||||
int lastLineno() const { return m_lastLineno; }
|
||||
int lastColumn() const { return m_lastColumn; }
|
||||
VFileContent* contentp() const { return m_contentp; }
|
||||
std::shared_ptr<VFileContent> contentp() const { return m_contentp; }
|
||||
// If not otherwise more specific, use last lineno for errors etc,
|
||||
// as the parser errors etc generally make more sense pointing at the last parse point
|
||||
int lineno() const { return m_lastLineno; }
|
||||
|
|
|
|||
|
|
@ -397,11 +397,11 @@ private:
|
|||
// Then propagate more complicated equations
|
||||
optimizeSignals(true);
|
||||
// Remove redundant logic
|
||||
if (v3Global.opt.oDedupe()) {
|
||||
if (v3Global.opt.fDedupe()) {
|
||||
dedupe();
|
||||
if (debug() >= 6) m_graph.dumpDotFilePrefixed("gate_dedup");
|
||||
}
|
||||
if (v3Global.opt.oAssemble()) {
|
||||
if (v3Global.opt.fAssemble()) {
|
||||
mergeAssigns();
|
||||
if (debug() >= 6) m_graph.dumpDotFilePrefixed("gate_assm");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ void GraphAcyc::simplify(bool allowCut) {
|
|||
if (allowCut) {
|
||||
// The main algorithm works without these, though slower
|
||||
// So if changing the main algorithm, comment these out for a test run
|
||||
if (v3Global.opt.oAcycSimp()) {
|
||||
if (v3Global.opt.fAcycSimp()) {
|
||||
cutBasic(vertexp);
|
||||
cutBackward(vertexp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -530,6 +530,6 @@ V3Hash V3Hasher::uncachedHash(const AstNode* nodep) {
|
|||
// This is used by the std::hash specialization for VNRef.
|
||||
// Declared separately to avoid a circular header dependency.
|
||||
|
||||
size_t V3HasherUncachedHash(AstNode& node) {
|
||||
size_t V3HasherUncachedHash(const AstNode& node) {
|
||||
return static_cast<size_t>(V3Hasher::uncachedHash(&node).value());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,8 +311,8 @@ private:
|
|||
// code will be emitted.
|
||||
UINFO(9, "assign to public and unpacked: " << nodep << endl);
|
||||
m_modp->addStmtp(
|
||||
new AstAssignW(flp, new AstVarRef(flp, exprvarrefp->varp(), VAccess::WRITE),
|
||||
new AstVarRef(flp, nodep, VAccess::READ)));
|
||||
new AstAssignW{flp, new AstVarRef{flp, nodep, VAccess::WRITE},
|
||||
new AstVarRef{flp, exprvarrefp->varp(), VAccess::READ}});
|
||||
} else if (nodep->isIfaceRef()) {
|
||||
m_modp->addStmtp(
|
||||
new AstAssignVarScope(flp, new AstVarRef(flp, nodep, VAccess::WRITE),
|
||||
|
|
|
|||
|
|
@ -218,10 +218,10 @@ public:
|
|||
void lifeToAbove() {
|
||||
// Any varrefs under a if/else branch affect statements outside and after the if/else
|
||||
if (!m_aboveLifep) v3fatalSrc("Pushing life when already at the top level");
|
||||
for (LifeMap::iterator it = m_map.begin(); it != m_map.end(); ++it) {
|
||||
AstVarScope* const nodep = it->first;
|
||||
for (auto& itr : m_map) {
|
||||
AstVarScope* const nodep = itr.first;
|
||||
m_aboveLifep->complexAssignFind(nodep);
|
||||
if (it->second.everSet()) {
|
||||
if (itr.second.everSet()) {
|
||||
// Record there may be an assignment, so we don't constant propagate across the if.
|
||||
complexAssignFind(nodep);
|
||||
} else {
|
||||
|
|
@ -235,14 +235,14 @@ public:
|
|||
// life1p->lifeDump();
|
||||
// life2p->lifeDump();
|
||||
AstNode::user1ClearTree(); // user1p() used on entire tree
|
||||
for (LifeMap::iterator it = life1p->m_map.begin(); it != life1p->m_map.end(); ++it) {
|
||||
for (auto& itr : life1p->m_map) {
|
||||
// When the if branch sets a var before it's used, mark that variable
|
||||
if (it->second.setBeforeUse()) it->first->user1(1);
|
||||
if (itr.second.setBeforeUse()) itr.first->user1(1);
|
||||
}
|
||||
for (LifeMap::iterator it = life2p->m_map.begin(); it != life2p->m_map.end(); ++it) {
|
||||
for (auto& itr : life2p->m_map) {
|
||||
// When the else branch sets a var before it's used
|
||||
AstVarScope* const nodep = it->first;
|
||||
if (it->second.setBeforeUse() && nodep->user1()) {
|
||||
AstVarScope* const nodep = itr.first;
|
||||
if (itr.second.setBeforeUse() && nodep->user1()) {
|
||||
// Both branches set the var, we can remove the assignment before the IF.
|
||||
UINFO(4, "DUALBRANCH " << nodep << endl);
|
||||
const auto itab = m_map.find(nodep);
|
||||
|
|
@ -251,14 +251,15 @@ public:
|
|||
}
|
||||
// this->lifeDump();
|
||||
}
|
||||
void clear() { m_map.clear(); }
|
||||
// DEBUG
|
||||
void lifeDump() {
|
||||
UINFO(5, " LifeMap:" << endl);
|
||||
for (LifeMap::iterator it = m_map.begin(); it != m_map.end(); ++it) {
|
||||
UINFO(5, " Ent: " << (it->second.setBeforeUse() ? "[F] " : " ") << it->first
|
||||
for (const auto& itr : m_map) {
|
||||
UINFO(5, " Ent: " << (itr.second.setBeforeUse() ? "[F] " : " ") << itr.first
|
||||
<< endl);
|
||||
if (it->second.assignp()) { //
|
||||
UINFO(5, " Ass: " << it->second.assignp() << endl);
|
||||
if (itr.second.assignp()) { //
|
||||
UINFO(5, " Ass: " << itr.second.assignp() << endl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -283,6 +284,10 @@ private:
|
|||
|
||||
// METHODS
|
||||
VL_DEBUG_FUNC; // Declare debug()
|
||||
void setNoopt() {
|
||||
m_noopt = true;
|
||||
m_lifep->clear();
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstVarRef* nodep) override {
|
||||
|
|
@ -386,13 +391,12 @@ private:
|
|||
// Just don't optimize blocks with labels; they're rare - so far.
|
||||
LifeBlock* const prevLifep = m_lifep;
|
||||
LifeBlock* const bodyLifep = new LifeBlock(prevLifep, m_statep);
|
||||
const bool prev_noopt = m_noopt;
|
||||
{
|
||||
VL_RESTORER(m_noopt);
|
||||
m_lifep = bodyLifep;
|
||||
m_noopt = true;
|
||||
setNoopt();
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
m_lifep = prevLifep;
|
||||
m_noopt = prev_noopt;
|
||||
}
|
||||
UINFO(4, " joinjump" << endl);
|
||||
// For the next assignments, clear any variables that were read or written in the block
|
||||
|
|
|
|||
|
|
@ -1478,11 +1478,11 @@ private:
|
|||
// Need to set pin numbers after varnames are created
|
||||
// But before we do the final resolution based on names
|
||||
VSymEnt* const foundp = m_statep->getNodeSym(m_modp)->findIdFlat(nodep->name());
|
||||
AstVar* const refp = foundp ? VN_AS(foundp->nodep(), Var) : nullptr;
|
||||
if (!refp) {
|
||||
AstVar* const refp = foundp ? VN_CAST(foundp->nodep(), Var) : nullptr;
|
||||
if (!foundp) {
|
||||
nodep->v3error(
|
||||
"Input/output/inout declaration not found for port: " << nodep->prettyNameQ());
|
||||
} else if (!refp->isIO() && !refp->isIfaceRef()) {
|
||||
} else if (!refp || (!refp->isIO() && !refp->isIfaceRef())) {
|
||||
nodep->v3error("Pin is not an in/out/inout/interface: " << nodep->prettyNameQ());
|
||||
} else {
|
||||
if (refp->user4()) {
|
||||
|
|
@ -1729,7 +1729,6 @@ class LinkDotScopeVisitor final : public VNVisitor {
|
|||
// Note we allow AstNodeStmt's as generates may be under them
|
||||
virtual void visit(AstCell*) override {}
|
||||
virtual void visit(AstVar*) override {}
|
||||
virtual void visit(AstNodeMath*) override {}
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -582,12 +582,12 @@ private:
|
|||
iterateChildren(nodep);
|
||||
nodep->timeunit(m_modp->timeunit());
|
||||
}
|
||||
virtual void visit(AstTimingControl* nodep) override {
|
||||
virtual void visit(AstEventControl* nodep) override {
|
||||
cleanFileline(nodep);
|
||||
iterateChildren(nodep);
|
||||
AstAlways* const alwaysp = VN_CAST(nodep->backp(), Always);
|
||||
if (alwaysp && alwaysp->keyword() == VAlwaysKwd::ALWAYS_COMB) {
|
||||
alwaysp->v3error("Timing control statements not legal under always_comb "
|
||||
alwaysp->v3error("Event control statements not legal under always_comb "
|
||||
"(IEEE 1800-2017 9.2.2.2.2)\n"
|
||||
<< nodep->warnMore() << "... Suggest use a normal 'always'");
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,34 @@
|
|||
//
|
||||
// Also merges consecutive AstNodeIf statements with the same condition.
|
||||
//
|
||||
// Because this optimization has notable performance impact, we go further
|
||||
// and perform code motion to try to move mergeable conditionals next to each
|
||||
// other, which in turn enable us to merge more conditionals. To do this, we
|
||||
// perform an analysis pass, followed by an optimization pass on the whole
|
||||
// AstCFunc we are optimizing.
|
||||
//
|
||||
// The analysis pass gathers, for each statement in the tree, the information
|
||||
// relevant for determining whether two statements can be swapped, and some
|
||||
// other additional information that is useful during optimization.
|
||||
//
|
||||
// The optimization pass tries to move conditionals near each other, first by
|
||||
// trying to move a conditional node backwards in the list, so it becomes the
|
||||
// direct successor of another earlier conditional with the same condition.
|
||||
// If this is not possible due to variable interference, then we additionally
|
||||
// try to pull earlier conditionals with the same condition closer forward to
|
||||
// be the immediate predecessor of the conditional node. We limit maximum
|
||||
// distance a node can travel to an empirically chosen but otherwise arbitrary
|
||||
// constant. This limits worst case complexity to be O(n) rather than O(n^2).
|
||||
// The worst case complexity manifests when N/2 conditionals, all with unique
|
||||
// conditions are succeeded by N/2 conditionals with the same unique
|
||||
// conditions, such that each unique condition is used by exactly 2
|
||||
// conditionals. In this case N/2 all nodes need to travel approx N/2 distance.
|
||||
// Limiting the distance bounds the latter, hence limiting complexity.
|
||||
//
|
||||
// Once the analysis and optimization passes have been applied to the whole
|
||||
// function, any merged conditionals will then undergo the same analysis,
|
||||
// optimization, and merging again in their individual branches.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "config_build.h"
|
||||
|
|
@ -51,71 +79,364 @@
|
|||
#include "V3MergeCond.h"
|
||||
#include "V3Stats.h"
|
||||
#include "V3Ast.h"
|
||||
#include "V3AstUserAllocator.h"
|
||||
#include "V3Hasher.h"
|
||||
#include "V3DupFinder.h"
|
||||
|
||||
#include <queue>
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
|
||||
//######################################################################
|
||||
// Utilities
|
||||
|
||||
enum class Mergeable {
|
||||
YES, // Tree can be merged
|
||||
NO_COND_ASSIGN, // Tree cannot be merged because it contains an assignment to a condition
|
||||
NO_IMPURE // Tree cannot be merged because it contains an impure node
|
||||
// This function extracts the Cond node from the RHS of an assignment,
|
||||
// if there is one and it is in a supported position, which are:
|
||||
// - RHS is the Cond
|
||||
// - RHS is And(Const, Cond). This And is inserted often by V3Clean.
|
||||
AstNodeCond* extractCondFromRhs(AstNode* rhsp) {
|
||||
if (AstNodeCond* const condp = VN_CAST(rhsp, NodeCond)) {
|
||||
return condp;
|
||||
} else if (const AstAnd* const andp = VN_CAST(rhsp, And)) {
|
||||
if (AstNodeCond* const condp = VN_CAST(andp->rhsp(), NodeCond)) {
|
||||
if (VN_IS(andp->lhsp(), Const)) return condp;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Predicate to check if two sets are disjoint. This is stable, as we only need
|
||||
// to determine if the sets contain a shared element, which is a boolean
|
||||
// property. It is also efficient as we use sorted sets, and therefore can
|
||||
// enumerate elements in order (what the ordering is, is unimportant), meaning
|
||||
// the worst case complexity is O(size of smaller set).
|
||||
bool areDisjoint(const std::set<const AstVar*>& a, const std::set<const AstVar*>& b) {
|
||||
if (a.empty() || b.empty()) return true;
|
||||
const auto endA = a.end();
|
||||
const auto endB = b.end();
|
||||
auto itA = a.begin();
|
||||
auto itB = b.begin();
|
||||
while (true) {
|
||||
if (*itA == *itB) return false;
|
||||
if (std::less<const AstVar*>{}(*itA, *itB)) {
|
||||
itA = std::lower_bound(++itA, endA, *itB);
|
||||
if (itA == endA) return true;
|
||||
} else {
|
||||
itB = std::lower_bound(++itB, endB, *itA);
|
||||
if (itB == endB) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// Structure containing information required for code motion/merging
|
||||
|
||||
struct StmtProperties {
|
||||
AstNode* m_condp = nullptr; // The condition expression, if a conditional node
|
||||
std::set<const AstVar*> m_rdVars; // Variables read by this statement
|
||||
std::set<const AstVar*> m_wrVars; // Variables writen by this statement
|
||||
bool m_isFence = false; // Nothing should move across this statement, nor should it be merged
|
||||
AstNodeStmt* m_prevWithSameCondp = nullptr; // Previous node in same list, with same condition
|
||||
bool writesConditionVar() const {
|
||||
// This relies on MarkVarsVisitor having been called on the condition node
|
||||
for (const AstVar* const varp : m_wrVars) {
|
||||
if (varp->user1()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class CheckMergeableVisitor final : public VNVisitor {
|
||||
private:
|
||||
// STATE
|
||||
bool m_condAssign = false; // Does this tree contain an assignment to a condition variable??
|
||||
bool m_impure = false; // Does this tree contain an impure node?
|
||||
// We store the statement properties in user3 via AstUser3Allocator
|
||||
using StmtPropertiesAllocator = AstUser3Allocator<AstNodeStmt, StmtProperties>;
|
||||
|
||||
// METHODS
|
||||
VL_DEBUG_FUNC; // Declare debug()
|
||||
//######################################################################
|
||||
// Code motion analysis and implementation
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstNode* nodep) override {
|
||||
if (m_impure) return;
|
||||
// Clear if node is impure
|
||||
if (!nodep->isPure()) {
|
||||
UINFO(9, "Not mergeable due to impure node" << nodep << endl);
|
||||
m_impure = true;
|
||||
return;
|
||||
// Pure analysis visitor that build the StmtProperties for each statement in the given
|
||||
// AstNode list (following AstNode::nextp())
|
||||
class CodeMotionAnalysisVisitor final : public VNVisitor {
|
||||
// NODE STATE
|
||||
// AstNodeStmt::user3 -> StmtProperties (accessed via m_stmtProperties, managed externally,
|
||||
// see MergeCondVisitor::process)
|
||||
// AstNode::user4 -> Used by V3Hasher
|
||||
// AstNode::user5 -> AstNode*: Set on a condition node, points to the last conditional
|
||||
// with that condition so far encountered in the same AstNode list
|
||||
|
||||
VNUser5InUse m_user5InUse;
|
||||
|
||||
StmtPropertiesAllocator& m_stmtProperties;
|
||||
|
||||
// MEMBERS
|
||||
V3Hasher m_hasher; // Used by V3DupFinder
|
||||
// Stack of a V3DupFinder used for finding identical condition expressions within one
|
||||
// statement list.
|
||||
std::vector<V3DupFinder> m_stack;
|
||||
StmtProperties* m_propsp = nullptr; // StmtProperties structure of current AstNodeStmt
|
||||
|
||||
// Extract condition expression from a megeable conditional statement, if any
|
||||
static AstNode* extractCondition(const AstNodeStmt* nodep) {
|
||||
AstNode* conditionp = nullptr;
|
||||
if (const AstNodeAssign* const assignp = VN_CAST(nodep, NodeAssign)) {
|
||||
if (AstNodeCond* const conditionalp = extractCondFromRhs(assignp->rhsp())) {
|
||||
conditionp = conditionalp->condp();
|
||||
}
|
||||
} else if (const AstNodeIf* const ifp = VN_CAST(nodep, NodeIf)) {
|
||||
conditionp = ifp->condp();
|
||||
}
|
||||
while (AstCCast* const castp = VN_CAST(conditionp, CCast)) conditionp = castp->lhsp();
|
||||
return conditionp;
|
||||
}
|
||||
|
||||
void analyzeStmt(AstNodeStmt* nodep, bool tryCondMatch) {
|
||||
VL_RESTORER(m_propsp);
|
||||
// Keep hold of props of enclosing statement
|
||||
StmtProperties* const outerPropsp = m_propsp;
|
||||
// Grab the props of this statement
|
||||
m_propsp = &m_stmtProperties(nodep);
|
||||
|
||||
// Extract condition from statement
|
||||
if (AstNode* const condp = extractCondition(nodep)) {
|
||||
// Remember condition node. We always need this as it is used in the later
|
||||
// traversal.
|
||||
m_propsp->m_condp = condp;
|
||||
// If this is a conditional statement, try to find an earlier one with the same
|
||||
// condition in the same list (unless we have been told not to bother because we know
|
||||
// this node is in a singleton list).
|
||||
if (tryCondMatch) {
|
||||
// Grab the duplicate finder of this list
|
||||
V3DupFinder& dupFinder = m_stack.back();
|
||||
// Find a duplicate condition
|
||||
const V3DupFinder::iterator& dit = dupFinder.findDuplicate(condp);
|
||||
if (dit == dupFinder.end()) {
|
||||
// First time seeing this condition in the current list
|
||||
dupFinder.insert(condp);
|
||||
// Remember last statement with this condition (which is this statement)
|
||||
condp->user5p(nodep);
|
||||
} else {
|
||||
// Seen a conditional with the same condition earlier in the current list
|
||||
AstNode* const firstp = dit->second;
|
||||
// Add to properties for easy retrieval during optimization
|
||||
m_propsp->m_prevWithSameCondp = static_cast<AstNodeStmt*>(firstp->user5p());
|
||||
// Remember last statement with this condition (which is this statement)
|
||||
firstp->user5p(nodep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyse this statement
|
||||
analyzeNode(nodep);
|
||||
|
||||
// If there is an enclosing statement, propagate properties upwards
|
||||
if (outerPropsp) {
|
||||
// Add all rd/wr vars to outer statement
|
||||
outerPropsp->m_rdVars.insert(m_propsp->m_rdVars.cbegin(), m_propsp->m_rdVars.cend());
|
||||
outerPropsp->m_wrVars.insert(m_propsp->m_wrVars.cbegin(), m_propsp->m_wrVars.cend());
|
||||
// If this statement is impure, the enclosing statement is also impure
|
||||
if (m_propsp->m_isFence) outerPropsp->m_isFence = true;
|
||||
}
|
||||
}
|
||||
|
||||
void analyzeVarRef(AstVarRef* nodep) {
|
||||
const VAccess access = nodep->access();
|
||||
AstVar* const varp = nodep->varp();
|
||||
// Gather read and written variables
|
||||
if (access.isReadOrRW()) m_propsp->m_rdVars.insert(varp);
|
||||
if (access.isWriteOrRW()) m_propsp->m_wrVars.insert(varp);
|
||||
}
|
||||
|
||||
void analyzeNode(AstNode* nodep) {
|
||||
// If an impure node under a statement, mark that statement as impure
|
||||
if (m_propsp && !nodep->isPure()) m_propsp->m_isFence = true;
|
||||
// Analyze children
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstVarRef* nodep) override {
|
||||
if (m_impure || m_condAssign) return;
|
||||
// Clear if it's an LValue referencing a marked variable
|
||||
if (nodep->access().isWriteOrRW() && nodep->varp()->user1()) {
|
||||
UINFO(9, "Not mergeable due assignment to condition" << nodep << endl);
|
||||
m_condAssign = true;
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNode* nodep) override {
|
||||
// Push a new stack entry at the start of a list, but only if the list is not a
|
||||
// single element (this saves a lot of allocations in expressions)
|
||||
bool singletonListStart = false;
|
||||
if (nodep->backp()->nextp() != nodep) { // If at head of list
|
||||
singletonListStart = nodep->nextp() == nullptr;
|
||||
if (!singletonListStart) m_stack.emplace_back(m_hasher);
|
||||
}
|
||||
|
||||
// Analyse node
|
||||
if (AstNodeStmt* const stmtp = VN_CAST(nodep, NodeStmt)) {
|
||||
analyzeStmt(stmtp, /*tryCondMatch:*/ !singletonListStart);
|
||||
} else if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
|
||||
analyzeVarRef(vrefp);
|
||||
} else {
|
||||
analyzeNode(nodep);
|
||||
}
|
||||
|
||||
// Pop the stack at the end of a list
|
||||
if (!singletonListStart && !nodep->nextp()) m_stack.pop_back();
|
||||
}
|
||||
|
||||
// CONSTRUCTOR
|
||||
CodeMotionAnalysisVisitor(AstNode* nodep, StmtPropertiesAllocator& stmtProperties)
|
||||
: m_stmtProperties(stmtProperties) {
|
||||
iterateAndNextConstNull(nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
CheckMergeableVisitor() = default;
|
||||
|
||||
// Return false if this node should not be merged at all because:
|
||||
// - It contains an impure expression
|
||||
// - It contains an LValue referencing the condition
|
||||
Mergeable operator()(const AstNode* node) {
|
||||
m_condAssign = false;
|
||||
m_impure = false;
|
||||
iterateChildrenConst(const_cast<AstNode*>(node));
|
||||
if (m_impure) { // Impure is stronger than cond assign
|
||||
return Mergeable::NO_IMPURE;
|
||||
} else if (m_condAssign) {
|
||||
return Mergeable::NO_COND_ASSIGN;
|
||||
} else {
|
||||
return Mergeable::YES;
|
||||
}
|
||||
// Analyse the statement list starting at nodep, filling in stmtProperties.
|
||||
static void analyze(AstNode* nodep, StmtPropertiesAllocator& stmtProperties) {
|
||||
CodeMotionAnalysisVisitor{nodep, stmtProperties};
|
||||
}
|
||||
};
|
||||
|
||||
class CodeMotionOptimizeVisitor final : public VNVisitor {
|
||||
// Do not move a node more than this many statements.
|
||||
// This bounds complexity at O(N), rather than O(N^2).
|
||||
static constexpr unsigned MAX_DISTANCE = 500;
|
||||
|
||||
// NODE STATE
|
||||
// AstNodeStmt::user3 -> StmtProperties (accessed via m_stmtProperties, managed externally,
|
||||
// see MergeCondVisitor::process)
|
||||
// AstNodeStmt::user4 -> bool: Already processed this node
|
||||
|
||||
VNUser4InUse m_user4InUse;
|
||||
|
||||
const StmtPropertiesAllocator& m_stmtProperties;
|
||||
|
||||
// MEMBERS
|
||||
|
||||
// Predicate that checks if the order of two statements can be swapped
|
||||
bool areSwappable(const AstNodeStmt* ap, const AstNodeStmt* bp) const {
|
||||
const StmtProperties& aProps = m_stmtProperties(ap);
|
||||
const StmtProperties& bProps = m_stmtProperties(bp);
|
||||
// Don't move across fences
|
||||
if (aProps.m_isFence) return false;
|
||||
if (bProps.m_isFence) return false;
|
||||
// If either statement writes a variable that the other reads, they are not swappable
|
||||
if (!areDisjoint(aProps.m_rdVars, bProps.m_wrVars)) return false;
|
||||
if (!areDisjoint(bProps.m_rdVars, aProps.m_wrVars)) return false;
|
||||
// If they both write to the same variable, they are not swappable
|
||||
if (!areDisjoint(aProps.m_wrVars, bProps.m_wrVars)) return false;
|
||||
// Otherwise good to go
|
||||
return true;
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNodeStmt* nodep) override {
|
||||
// Process only on first encounter
|
||||
if (nodep->user4SetOnce()) return;
|
||||
// First re-order children
|
||||
iterateChildren(nodep);
|
||||
// Grab hold of previous node with same condition
|
||||
AstNodeStmt* prevp = m_stmtProperties(nodep).m_prevWithSameCondp;
|
||||
// If no previous node with same condition, we are done
|
||||
if (!prevp) return;
|
||||
#ifdef VL_DEBUG
|
||||
{ // Sanity check, only in debug build, otherwise expensive
|
||||
const AstNode* currp = prevp;
|
||||
while (currp && currp != nodep) currp = currp->nextp();
|
||||
UASSERT_OBJ(currp, nodep, "Predecessor not in same list as " << currp);
|
||||
}
|
||||
#endif
|
||||
// Otherwise try to move this node backwards, as close as we can to the previous node
|
||||
// with the same condition
|
||||
if (AstNodeStmt* predp = VN_CAST(nodep->backp(), NodeStmt)) {
|
||||
// 'predp' is the newly computed predecessor node of 'nodep', which is initially
|
||||
// (without movement) the 'backp' of the node.
|
||||
for (unsigned i = MAX_DISTANCE; i; --i) {
|
||||
// If the predecessor is the previous node with the same condition, job done
|
||||
if (predp == prevp) break;
|
||||
// Don't move past a non-statement (e.g.: AstVar), or end of list
|
||||
AstNodeStmt* const backp = VN_CAST(predp->backp(), NodeStmt);
|
||||
if (!backp) break;
|
||||
// Don't swap statements if doing so would change program semantics
|
||||
if (!areSwappable(predp, nodep)) break;
|
||||
// Otherwise move 'nodep' back
|
||||
predp = backp;
|
||||
}
|
||||
|
||||
// If we decided that 'nodep' should be moved back
|
||||
if (nodep->backp() != predp) {
|
||||
// Move the current node to directly follow the computed predecessor
|
||||
nodep->unlinkFrBack();
|
||||
predp->addNextHere(nodep);
|
||||
// If the predecessor is the previous node with the same condition, job done
|
||||
if (predp == prevp) return;
|
||||
}
|
||||
}
|
||||
// If we reach here, it means we were unable to move the current node all the way back
|
||||
// such that it immediately follows the previous statement with the same condition. Now
|
||||
// try to move all previous statements with the same condition forward, in the hope of
|
||||
// compacting the list further.
|
||||
for (AstNodeStmt* currp = nodep; prevp;
|
||||
currp = prevp, prevp = m_stmtProperties(currp).m_prevWithSameCondp) {
|
||||
// Move prevp (previous statement with same condition) towards currp
|
||||
if (AstNodeStmt* succp = VN_CAST(prevp->nextp(), NodeStmt)) {
|
||||
// 'succp' is the newly computed successor node of 'prevp', which is initially
|
||||
// (without movement) the 'nextp' of the node.
|
||||
for (unsigned i = MAX_DISTANCE; --i;) {
|
||||
// If the successor of the previous statement with same condition is the
|
||||
// target node, we are done with this predecessor
|
||||
if (succp == currp) break;
|
||||
// Don't move past a non-statement (e.g.: AstVar), or end of list
|
||||
AstNodeStmt* const nextp = VN_CAST(succp->nextp(), NodeStmt);
|
||||
if (!nextp) break;
|
||||
// Don't swap statements if doing so would change program semantics
|
||||
if (!areSwappable(prevp, succp)) break;
|
||||
// Otherwise move further forward
|
||||
succp = nextp;
|
||||
}
|
||||
|
||||
// If we decided that 'prevp' should be moved forward
|
||||
if (prevp->nextp() != succp) {
|
||||
// Move the current node to directly before the computed successor
|
||||
prevp->unlinkFrBack();
|
||||
succp->addHereThisAsNext(prevp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstNode* nodep) override {} // Ignore all non-statements
|
||||
|
||||
// CONSTRUCTOR
|
||||
CodeMotionOptimizeVisitor(AstNode* nodep, const StmtPropertiesAllocator& stmtProperties)
|
||||
: m_stmtProperties(stmtProperties) {
|
||||
// We assert the given node is at the head of the list otherwise we might move a node
|
||||
// before the given node. This is easy to fix in the above iteration with a check on a
|
||||
// boundary node we should not move past, if we ever need to do so.
|
||||
// Note: we will do iterateAndNextNull which requires nodep->backp() != nullptr anyway
|
||||
UASSERT_OBJ(nodep->backp()->nextp() != nodep, nodep, "Must be at head of list");
|
||||
// Optimize the list
|
||||
iterateAndNextNull(nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
// Given an AstNode list (held via AstNode::nextp()), move conditional statements as close
|
||||
// together as possible
|
||||
static AstNode* optimize(AstNode* nodep, const StmtPropertiesAllocator& stmtProperties) {
|
||||
CodeMotionOptimizeVisitor{nodep, stmtProperties};
|
||||
// It is possible for the head of the list to be moved later such that it is no longer
|
||||
// in head position. If so, rewind the list and return the new head.
|
||||
while (nodep->backp()->nextp() == nodep) nodep = nodep->backp();
|
||||
return nodep;
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Conditional merging
|
||||
|
||||
class MergeCondVisitor final : public VNVisitor {
|
||||
private:
|
||||
// NODE STATE
|
||||
// AstVar::user1 -> Flag set for variables referenced by m_mgCondp
|
||||
// AstNode::user2 -> Flag marking node as included in merge because cheap to duplicate
|
||||
const VNUser1InUse m_user1InUse;
|
||||
const VNUser2InUse m_user2InUse;
|
||||
// AstVar::user1 -> bool: Set for variables referenced by m_mgCondp
|
||||
// (Only below MergeCondVisitor::process).
|
||||
// AstNode::user2 -> bool: Marking node as included in merge because cheap to
|
||||
// duplicate
|
||||
// (Only below MergeCondVisitor::process).
|
||||
// AstNodeStmt::user3 -> StmtProperties
|
||||
// (Only below MergeCondVisitor::process).
|
||||
// AstNode::user4 -> See CodeMotionAnalysisVisitor/CodeMotionOptimizeVisitor
|
||||
// AstNode::user5 -> See CodeMotionAnalysisVisitor
|
||||
|
||||
// STATE
|
||||
VDouble0 m_statMerges; // Statistic tracking
|
||||
|
|
@ -128,24 +449,86 @@ private:
|
|||
const AstNode* m_mgNextp = nullptr; // Next node in list being examined
|
||||
uint32_t m_listLenght = 0; // Length of current list
|
||||
|
||||
CheckMergeableVisitor m_checkMergeable; // Sub visitor for encapsulation & speed
|
||||
std::queue<AstNode*>* m_workQueuep = nullptr; // Node lists (via AstNode::nextp()) to merge
|
||||
// Statement properties for code motion and merging
|
||||
StmtPropertiesAllocator* m_stmtPropertiesp = nullptr;
|
||||
|
||||
// METHODS
|
||||
VL_DEBUG_FUNC; // Declare debug()
|
||||
|
||||
// This function extracts the Cond node from the RHS, if there is one and
|
||||
// it is in a supported position, which are:
|
||||
// - RHS is the Cond
|
||||
// - RHS is And(Const, Cond). This And is inserted often by V3Clean.
|
||||
static AstNodeCond* extractCond(AstNode* rhsp) {
|
||||
if (AstNodeCond* const condp = VN_CAST(rhsp, NodeCond)) {
|
||||
return condp;
|
||||
} else if (const AstAnd* const andp = VN_CAST(rhsp, And)) {
|
||||
if (AstNodeCond* const condp = VN_CAST(andp->rhsp(), NodeCond)) {
|
||||
if (VN_IS(andp->lhsp(), Const)) return condp;
|
||||
// Function that processes a whole sub-tree
|
||||
void process(AstNode* nodep) {
|
||||
// Set up work queue
|
||||
std::queue<AstNode*> workQueue;
|
||||
m_workQueuep = &workQueue;
|
||||
m_workQueuep->push(nodep);
|
||||
|
||||
do {
|
||||
// Set up user* for this iteration
|
||||
const VNUser1InUse user1InUse;
|
||||
const VNUser2InUse user2InUse;
|
||||
const VNUser3InUse user3InUse;
|
||||
// Statement properties only preserved for this iteration,
|
||||
// then memory is released immediately.
|
||||
StmtPropertiesAllocator stmtProperties;
|
||||
m_stmtPropertiesp = &stmtProperties;
|
||||
|
||||
// Pop off current work item
|
||||
AstNode* currp = m_workQueuep->front();
|
||||
m_workQueuep->pop();
|
||||
|
||||
// Analyse sub-tree list for code motion and conditional merging
|
||||
CodeMotionAnalysisVisitor::analyze(currp, stmtProperties);
|
||||
// Perform the code motion within the whole sub-tree list
|
||||
if (v3Global.opt.fMergeCondMotion()) {
|
||||
currp = CodeMotionOptimizeVisitor::optimize(currp, stmtProperties);
|
||||
}
|
||||
|
||||
// Merge conditionals in the whole sub-tree list (this might create new work items)
|
||||
iterateAndNextNull(currp);
|
||||
|
||||
// Close pending merge, if there is one at the end of the whole sub-tree list
|
||||
if (m_mgFirstp) mergeEnd();
|
||||
} while (!m_workQueuep->empty());
|
||||
}
|
||||
|
||||
// Skip past AstArraySel and AstWordSel with const index
|
||||
static AstNode* skipConstSels(AstNode* nodep) {
|
||||
while (const AstArraySel* const aselp = VN_CAST(nodep, ArraySel)) {
|
||||
// ArraySel index is not constant, so might be expensive
|
||||
if (!VN_IS(aselp->bitp(), Const)) return nodep;
|
||||
nodep = aselp->fromp();
|
||||
}
|
||||
return nullptr;
|
||||
while (const AstWordSel* const wselp = VN_CAST(nodep, WordSel)) {
|
||||
// WordSel index is not constant, so might be expensive
|
||||
if (!VN_IS(wselp->bitp(), Const)) return nodep;
|
||||
nodep = wselp->fromp();
|
||||
}
|
||||
return nodep;
|
||||
}
|
||||
|
||||
// Check if this node is cheap enough that duplicating it in two branches of an
|
||||
// AstIf is not likely to cause a performance degradation.
|
||||
static bool isCheapNode(AstNode* nodep) {
|
||||
// Comments are cheap
|
||||
if (VN_IS(nodep, Comment)) return true;
|
||||
// So are some assignments
|
||||
if (const AstNodeAssign* const assignp = VN_CAST(nodep, NodeAssign)) {
|
||||
// Check LHS
|
||||
AstNode* const lhsp = skipConstSels(assignp->lhsp());
|
||||
// LHS is not a VarRef, so might be expensive
|
||||
if (!VN_IS(lhsp, VarRef)) return false;
|
||||
|
||||
// Check RHS
|
||||
AstNode* const rhsp = skipConstSels(assignp->rhsp());
|
||||
// RHS is not a VarRef or Constant so might be expensive
|
||||
if (!VN_IS(rhsp, VarRef) && !VN_IS(rhsp, Const)) return false;
|
||||
|
||||
// Otherwise it is a cheap assignment
|
||||
return true;
|
||||
}
|
||||
// Others are not
|
||||
return false;
|
||||
}
|
||||
|
||||
// Predicate to check if an expression yields only 0 or 1 (i.e.: a 1-bit value)
|
||||
|
|
@ -196,23 +579,21 @@ private:
|
|||
static AstNode* maskLsb(AstNode* nodep) {
|
||||
if (yieldsOneOrZero(nodep)) return nodep;
|
||||
// Otherwise apply masking
|
||||
AstNode* const maskp = new AstConst(nodep->fileline(), AstConst::BitTrue());
|
||||
AstNode* const maskp = new AstConst{nodep->fileline(), AstConst::BitTrue()};
|
||||
// Mask on left, as conventional
|
||||
return new AstAnd(nodep->fileline(), maskp, nodep);
|
||||
return new AstAnd{nodep->fileline(), maskp, nodep};
|
||||
}
|
||||
|
||||
// Fold the RHS expression assuming the given condition state. Unlink bits
|
||||
// from the RHS which is only used once, and can be reused. What remains
|
||||
// of the RHS is expected to be deleted by the caller.
|
||||
// Fold the RHS expression of an assignment assuming the given condition state.
|
||||
// Unlink bits from the RHS which is only used once, and can be reused (is an unomdified
|
||||
// sub-tree). What remains of the RHS is expected to be deleted by the caller.
|
||||
AstNode* foldAndUnlink(AstNode* rhsp, bool condTrue) {
|
||||
if (rhsp->sameTree(m_mgCondp)) {
|
||||
return new AstConst(rhsp->fileline(), AstConst::BitTrue{}, condTrue);
|
||||
} else if (const AstNodeCond* const condp = extractCond(rhsp)) {
|
||||
return new AstConst{rhsp->fileline(), AstConst::BitTrue{}, condTrue};
|
||||
} else if (const AstNodeCond* const condp = extractCondFromRhs(rhsp)) {
|
||||
AstNode* const resp
|
||||
= condTrue ? condp->expr1p()->unlinkFrBack() : condp->expr2p()->unlinkFrBack();
|
||||
if (condp == rhsp) { //
|
||||
return resp;
|
||||
}
|
||||
if (condp == rhsp) return resp;
|
||||
if (const AstAnd* const andp = VN_CAST(rhsp, And)) {
|
||||
UASSERT_OBJ(andp->rhsp() == condp, rhsp, "Should not try to fold this");
|
||||
return new AstAnd{andp->fileline(), andp->lhsp()->cloneTree(false), resp};
|
||||
|
|
@ -227,17 +608,18 @@ private:
|
|||
return condTrue ? maskLsb(andp->lhsp()->unlinkFrBack())
|
||||
: new AstConst{rhsp->fileline(), AstConst::BitFalse()};
|
||||
}
|
||||
} else if (VN_IS(rhsp, WordSel) || VN_IS(rhsp, VarRef) || VN_IS(rhsp, Const)) {
|
||||
} else if (VN_IS(rhsp, ArraySel) || VN_IS(rhsp, WordSel) || VN_IS(rhsp, VarRef)
|
||||
|| VN_IS(rhsp, Const)) {
|
||||
return rhsp->cloneTree(false);
|
||||
}
|
||||
rhsp->dumpTree("Don't know how to fold expression: ");
|
||||
rhsp->v3fatalSrc("Don't know how to fold expression");
|
||||
// LCOV_EXCL_START
|
||||
if (debug()) rhsp->dumpTree("Don't know how to fold expression: ");
|
||||
rhsp->v3fatalSrc("Should not try to fold this during conditional merging");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
void mergeEnd(int lineno) {
|
||||
UASSERT(m_mgFirstp, "mergeEnd without list " << lineno);
|
||||
// We might want to recursively merge an AstIf. We stash it in this variable.
|
||||
const AstNodeIf* recursivep = nullptr;
|
||||
void mergeEnd() {
|
||||
UASSERT(m_mgFirstp, "mergeEnd without list");
|
||||
// Drop leading cheap nodes. These were only added in the hope of finding
|
||||
// an earlier reduced form, but we failed to do so.
|
||||
while (m_mgFirstp->user2() && m_mgFirstp != m_mgLastp) {
|
||||
|
|
@ -254,8 +636,11 @@ private:
|
|||
m_mgLastp = m_mgLastp->backp();
|
||||
--m_listLenght;
|
||||
UASSERT_OBJ(m_mgLastp && m_mgLastp->nextp() == nextp, m_mgFirstp,
|
||||
"Cheap assignment should not be at the front of the list");
|
||||
"Cheap statement should not be at the front of the list");
|
||||
}
|
||||
// If the list contains a single AstNodeIf, we will want to merge its branches.
|
||||
// If so, keep hold of the AstNodeIf in this variable.
|
||||
AstNodeIf* recursivep = nullptr;
|
||||
// Merge if list is longer than one node
|
||||
if (m_mgFirstp != m_mgLastp) {
|
||||
UINFO(6, "MergeCond - First: " << m_mgFirstp << " Last: " << m_mgLastp << endl);
|
||||
|
|
@ -266,7 +651,7 @@ private:
|
|||
// and we also need to keep track of it for comparisons later.
|
||||
m_mgCondp = m_mgCondp->cloneTree(false);
|
||||
// Create equivalent 'if' statement and insert it before the first node
|
||||
AstIf* const resultp = new AstIf(m_mgCondp->fileline(), m_mgCondp);
|
||||
AstIf* const resultp = new AstIf{m_mgCondp->fileline(), m_mgCondp};
|
||||
m_mgFirstp->addHereThisAsNext(resultp);
|
||||
// Unzip the list and insert under branches
|
||||
AstNode* nextp = m_mgFirstp;
|
||||
|
|
@ -308,10 +693,12 @@ private:
|
|||
VL_DO_DANGLING(ifp->deleteTree(), ifp);
|
||||
}
|
||||
} while (nextp);
|
||||
// Recursively merge the resulting AstIf
|
||||
recursivep = resultp;
|
||||
} else if (const AstNodeIf* const ifp = VN_CAST(m_mgFirstp, NodeIf)) {
|
||||
// There was nothing to merge this AstNodeIf with, but try to merge it's branches
|
||||
// Merge the branches of the resulting AstIf after re-analysis
|
||||
if (resultp->ifsp()) m_workQueuep->push(resultp->ifsp());
|
||||
if (resultp->elsesp()) m_workQueuep->push(resultp->elsesp());
|
||||
} else if (AstNodeIf* const ifp = VN_CAST(m_mgFirstp, NodeIf)) {
|
||||
// There was nothing to merge this AstNodeIf with, so try to merge its branches.
|
||||
// No re-analysis is required for this, so do it directly below
|
||||
recursivep = ifp;
|
||||
}
|
||||
// Reset state
|
||||
|
|
@ -321,14 +708,13 @@ private:
|
|||
m_mgNextp = nullptr;
|
||||
AstNode::user1ClearTree(); // Clear marked variables
|
||||
AstNode::user2ClearTree();
|
||||
// Merge recursively within the branches
|
||||
// Merge recursively within the branches of an un-merged AstNodeIF
|
||||
if (recursivep) {
|
||||
iterateAndNextNull(recursivep->ifsp());
|
||||
// Close list, if there is one at the end of the then branch
|
||||
if (m_mgFirstp) mergeEnd(__LINE__);
|
||||
iterateAndNextNull(recursivep->elsesp());
|
||||
// Close list, if there is one at the end of the else branch
|
||||
if (m_mgFirstp) mergeEnd(__LINE__);
|
||||
// Close a pending merge to ensure merge state is
|
||||
// reset as expected at the end of this function
|
||||
if (m_mgFirstp) mergeEnd();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -351,51 +737,31 @@ private:
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check if this node is cheap enough that duplicating it in two branches of an
|
||||
// AstIf and is hence not likely to cause a performance degradation if doing so.
|
||||
bool isCheapNode(AstNode* nodep) const {
|
||||
if (VN_IS(nodep, Comment)) return true;
|
||||
if (const AstNodeAssign* const assignp = VN_CAST(nodep, NodeAssign)) {
|
||||
// Check LHS
|
||||
AstNode* lhsp = assignp->lhsp();
|
||||
while (AstWordSel* const wselp = VN_CAST(lhsp, WordSel)) {
|
||||
// WordSel index is not constant, so might be expensive
|
||||
if (!VN_IS(wselp->bitp(), Const)) return false;
|
||||
lhsp = wselp->fromp();
|
||||
}
|
||||
// LHS is not a VarRef, so might be expensive
|
||||
if (!VN_IS(lhsp, VarRef)) return false;
|
||||
|
||||
// Check RHS
|
||||
AstNode* rhsp = assignp->rhsp();
|
||||
while (AstWordSel* const wselp = VN_CAST(rhsp, WordSel)) {
|
||||
// WordSel index is not constant, so might be expensive
|
||||
if (!VN_IS(wselp->bitp(), Const)) return false;
|
||||
rhsp = wselp->fromp();
|
||||
}
|
||||
// RHS is not a VarRef or Constant so might be expensive
|
||||
if (!VN_IS(rhsp, VarRef) && !VN_IS(rhsp, Const)) return false;
|
||||
|
||||
// Otherwise it is a cheap assignment
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void addToList(AstNode* nodep, AstNode* condp, int line) {
|
||||
bool addToList(AstNodeStmt* nodep, AstNode* condp) {
|
||||
// Set up head of new list if node is first in list
|
||||
if (!m_mgFirstp) {
|
||||
UASSERT_OBJ(condp, nodep, "Cannot start new list without condition " << line);
|
||||
UASSERT_OBJ(condp, nodep, "Cannot start new list without condition");
|
||||
// Mark variable references in the condition
|
||||
condp->foreach<AstVarRef>([](const AstVarRef* nodep) { nodep->varp()->user1(1); });
|
||||
// Now check again if mergeable. We need this to pick up assignments to conditions,
|
||||
// e.g.: 'c = c ? a : b' at the beginning of the list, which is in fact not mergeable
|
||||
// because it updates the condition. We simply bail on these.
|
||||
if ((*m_stmtPropertiesp)(nodep).writesConditionVar()) {
|
||||
// Clear marked variables
|
||||
AstNode::user1ClearTree();
|
||||
// We did not add to the list
|
||||
return false;
|
||||
}
|
||||
m_mgFirstp = nodep;
|
||||
m_mgCondp = condp;
|
||||
m_listLenght = 0;
|
||||
// Mark variable references in the condition
|
||||
condp->foreach<AstVarRef>([](const AstVarRef* nodep) { nodep->varp()->user1(1); });
|
||||
// Add any preceding nodes to the list that would allow us to extend the merge range
|
||||
for (;;) {
|
||||
AstNode* const backp = m_mgFirstp->backp();
|
||||
// Add any preceding nodes to the list that would allow us to extend the merge
|
||||
// range
|
||||
while (true) {
|
||||
AstNodeStmt* const backp = VN_CAST(m_mgFirstp->backp(), NodeStmt);
|
||||
if (!backp || backp->nextp() != m_mgFirstp) break; // Don't move up the tree
|
||||
if (m_checkMergeable(backp) != Mergeable::YES) break;
|
||||
const StmtProperties& props = (*m_stmtPropertiesp)(backp);
|
||||
if (props.m_isFence || props.writesConditionVar()) break;
|
||||
if (isSimplifiableNode(backp)) {
|
||||
++m_listLenght;
|
||||
m_mgFirstp = backp;
|
||||
|
|
@ -415,60 +781,53 @@ private:
|
|||
// Set up expected next node in list.
|
||||
m_mgNextp = nodep->nextp();
|
||||
// If last under parent, done with current list
|
||||
if (!m_mgNextp) mergeEnd(__LINE__);
|
||||
if (!m_mgNextp) mergeEnd();
|
||||
// We did add to the list
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this node is the next expected node and is helpful to add to the list, do so,
|
||||
// otherwise end the current merge. Return ture if added, false if ended merge.
|
||||
bool addIfHelpfulElseEndMerge(AstNode* nodep) {
|
||||
bool addIfHelpfulElseEndMerge(AstNodeStmt* nodep) {
|
||||
UASSERT_OBJ(m_mgFirstp, nodep, "List must be open");
|
||||
if (m_mgNextp == nodep) {
|
||||
if (isSimplifiableNode(nodep)) {
|
||||
addToList(nodep, nullptr, __LINE__);
|
||||
return true;
|
||||
}
|
||||
if (isCheapNode(nodep)) {
|
||||
if (addToList(nodep, nullptr)) return true;
|
||||
} else if (isCheapNode(nodep)) {
|
||||
nodep->user2(1);
|
||||
addToList(nodep, nullptr, __LINE__);
|
||||
return true;
|
||||
if (addToList(nodep, nullptr)) return true;
|
||||
}
|
||||
}
|
||||
// Not added to list, so we are done with the current list
|
||||
mergeEnd(__LINE__);
|
||||
mergeEnd();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool checkOrMakeMergeable(AstNode* nodep) {
|
||||
const Mergeable reason = m_checkMergeable(nodep);
|
||||
// If meregeable, we are done
|
||||
if (reason == Mergeable::YES) return true;
|
||||
// Node not mergeable.
|
||||
// If no current list, then this node is just special, move on.
|
||||
if (!m_mgFirstp) return false;
|
||||
// Otherwise finish current list
|
||||
mergeEnd(__LINE__);
|
||||
// If a tree was not mergeable due to an assignment to a condition,
|
||||
// then finishing the current list makes it mergeable again.
|
||||
return reason == Mergeable::NO_COND_ASSIGN;
|
||||
bool checkOrMakeMergeable(const AstNodeStmt* nodep) {
|
||||
const StmtProperties& props = (*m_stmtPropertiesp)(nodep);
|
||||
if (props.m_isFence) return false; // Fence node never mergeable
|
||||
// If the statement writes a condition variable of a pending merge,
|
||||
// we must end the pending merge
|
||||
if (m_mgFirstp && props.writesConditionVar()) mergeEnd();
|
||||
return true; // Now surely mergeable
|
||||
}
|
||||
|
||||
void mergeEndIfIncompatible(AstNode* nodep, AstNode* condp) {
|
||||
void mergeEndIfIncompatible(const AstNode* nodep, const AstNode* condp) {
|
||||
if (m_mgFirstp && (m_mgNextp != nodep || !condp->sameTree(m_mgCondp))) {
|
||||
// Node in different list, or has different condition. Finish current list.
|
||||
mergeEnd(__LINE__);
|
||||
mergeEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
AstNode* const rhsp = nodep->rhsp();
|
||||
if (const AstNodeCond* const condp = extractCond(rhsp)) {
|
||||
if (AstNode* const condp = (*m_stmtPropertiesp)(nodep).m_condp) {
|
||||
// Check if mergeable
|
||||
if (!checkOrMakeMergeable(nodep)) return;
|
||||
// Close potentially incompatible pending merge
|
||||
mergeEndIfIncompatible(nodep, condp->condp());
|
||||
mergeEndIfIncompatible(nodep, condp);
|
||||
// Add current node
|
||||
addToList(nodep, condp->condp(), __LINE__);
|
||||
addToList(nodep, condp);
|
||||
} else if (m_mgFirstp) {
|
||||
addIfHelpfulElseEndMerge(nodep);
|
||||
}
|
||||
|
|
@ -485,21 +844,22 @@ private:
|
|||
// Close potentially incompatible pending merge
|
||||
mergeEndIfIncompatible(nodep, nodep->condp());
|
||||
// Add current node
|
||||
addToList(nodep, nodep->condp(), __LINE__);
|
||||
addToList(nodep, nodep->condp());
|
||||
}
|
||||
|
||||
virtual void visit(AstNodeStmt* nodep) override {
|
||||
if (m_mgFirstp && addIfHelpfulElseEndMerge(nodep)) return;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
virtual void visit(AstCFunc* nodep) override {
|
||||
// Merge function body
|
||||
if (nodep->stmtsp()) process(nodep->stmtsp());
|
||||
}
|
||||
|
||||
// For speed, only iterate what is necessary.
|
||||
virtual void visit(AstNetlist* nodep) override { iterateAndNextNull(nodep->modulesp()); }
|
||||
virtual void visit(AstNodeModule* nodep) override { iterateAndNextNull(nodep->stmtsp()); }
|
||||
virtual void visit(AstCFunc* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
// Close list, if there is one at the end of the function
|
||||
if (m_mgFirstp) mergeEnd(__LINE__);
|
||||
}
|
||||
virtual void visit(AstNodeStmt* nodep) override {
|
||||
if (m_mgFirstp && addIfHelpfulElseEndMerge(nodep)) return;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstNode* nodep) override {}
|
||||
|
||||
public:
|
||||
|
|
@ -512,6 +872,8 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
//######################################################################
|
||||
// MergeConditionals class functions
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ struct V3OptionParser::Impl {
|
|||
// Setting for isOnOffAllowed() and isPartialMatchAllowed()
|
||||
enum class en : uint8_t {
|
||||
NONE, // "-opt"
|
||||
FONOFF, // "-fopt" and "-fno-opt"
|
||||
ONOFF, // "-opt" and "-no-opt"
|
||||
VALUE // "-opt val"
|
||||
};
|
||||
|
|
@ -39,6 +40,7 @@ struct V3OptionParser::Impl {
|
|||
bool m_undocumented = false; // This option is not documented
|
||||
public:
|
||||
virtual bool isValueNeeded() const override final { return MODE == en::VALUE; }
|
||||
virtual bool isFOnOffAllowed() const override final { return MODE == en::FONOFF; }
|
||||
virtual bool isOnOffAllowed() const override final { return MODE == en::ONOFF; }
|
||||
virtual bool isPartialMatchAllowed() const override final { return ALLOW_PARTIAL_MATCH; }
|
||||
virtual bool isUndocumented() const override { return m_undocumented; }
|
||||
|
|
@ -47,6 +49,7 @@ struct V3OptionParser::Impl {
|
|||
|
||||
// Actual action classes
|
||||
template <typename T> class ActionSet; // "-opt" for bool-ish, "-opt val" for int and string
|
||||
template <typename BOOL> class ActionFOnOff; // "-fopt" and "-fno-opt" for bool-ish
|
||||
template <typename BOOL> class ActionOnOff; // "-opt" and "-no-opt" for bool-ish
|
||||
class ActionCbCall; // Callback without argument for "-opt"
|
||||
class ActionCbOnOff; // Callback for "-opt" and "-no-opt"
|
||||
|
|
@ -80,6 +83,7 @@ V3OPTION_PARSER_DEF_ACT_CLASS(ActionSet, VOptionBool, m_valp->setTrueOrFalse(tru
|
|||
V3OPTION_PARSER_DEF_ACT_CLASS(ActionSet, int, *m_valp = std::atoi(argp), en::VALUE);
|
||||
V3OPTION_PARSER_DEF_ACT_CLASS(ActionSet, string, *m_valp = argp, en::VALUE);
|
||||
|
||||
V3OPTION_PARSER_DEF_ACT_CLASS(ActionFOnOff, bool, *m_valp = !hasPrefixFNo(optp), en::FONOFF);
|
||||
V3OPTION_PARSER_DEF_ACT_CLASS(ActionOnOff, bool, *m_valp = !hasPrefixNo(optp), en::ONOFF);
|
||||
#ifndef V3OPTION_PARSER_NO_VOPTION_BOOL
|
||||
V3OPTION_PARSER_DEF_ACT_CLASS(ActionOnOff, VOptionBool, m_valp->setTrueOrFalse(!hasPrefixNo(optp)),
|
||||
|
|
@ -117,12 +121,23 @@ V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbPartialMatchVal, void(const char*, cons
|
|||
|
||||
V3OptionParser::ActionIfs* V3OptionParser::find(const char* optp) {
|
||||
const auto it = m_pimpl->m_options.find(optp);
|
||||
if (it != m_pimpl->m_options.end()) return it->second.get();
|
||||
if (it != m_pimpl->m_options.end()) return it->second.get(); // Exact match
|
||||
for (auto&& act : m_pimpl->m_options) {
|
||||
if (act.second->isFOnOffAllowed()) { // Find starts with "-fno"
|
||||
if (const char* const nop
|
||||
= VString::startsWith(optp, "-fno-") ? (optp + strlen("-fno-")) : nullptr) {
|
||||
if (act.first.substr(strlen("-f"), std::string::npos)
|
||||
== nop) { // [-f]opt = [-fno-]opt
|
||||
return act.second.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (act.second->isOnOffAllowed()) { // Find starts with "-no"
|
||||
const char* const nop = VString::startsWith(optp, "-no") ? (optp + 3) : nullptr;
|
||||
if (nop && (act.first == nop || act.first == (string{"-"} + nop))) {
|
||||
return act.second.get();
|
||||
if (const char* const nop
|
||||
= VString::startsWith(optp, "-no") ? (optp + strlen("-no")) : nullptr) {
|
||||
if (act.first == nop || act.first == (string{"-"} + nop)) {
|
||||
return act.second.get();
|
||||
}
|
||||
}
|
||||
} else if (act.second->isPartialMatchAllowed()) {
|
||||
if (VString::startsWith(optp, act.first)) return act.second.get();
|
||||
|
|
@ -143,6 +158,12 @@ V3OptionParser::ActionIfs& V3OptionParser::add(const std::string& opt, ARG arg)
|
|||
return *insertedResult.first->second;
|
||||
}
|
||||
|
||||
bool V3OptionParser::hasPrefixFNo(const char* strp) {
|
||||
UASSERT(strp[0] == '-', strp << " does not start with '-'");
|
||||
if (strp[1] == '-') ++strp;
|
||||
return VString::startsWith(strp, "-fno");
|
||||
}
|
||||
|
||||
bool V3OptionParser::hasPrefixNo(const char* strp) {
|
||||
UASSERT(strp[0] == '-', strp << " does not start with '-'");
|
||||
if (strp[1] == '-') ++strp;
|
||||
|
|
@ -178,6 +199,10 @@ void V3OptionParser::finalize() {
|
|||
for (auto&& opt : m_pimpl->m_options) {
|
||||
if (opt.second->isUndocumented()) continue;
|
||||
m_pimpl->m_spellCheck.pushCandidate(opt.first);
|
||||
if (opt.second->isFOnOffAllowed()) {
|
||||
m_pimpl->m_spellCheck.pushCandidate(
|
||||
"-fno-" + opt.first.substr(strlen("-f"), std::string::npos));
|
||||
}
|
||||
if (opt.second->isOnOffAllowed()) m_pimpl->m_spellCheck.pushCandidate("-no" + opt.first);
|
||||
}
|
||||
m_pimpl->m_isFinalized = true;
|
||||
|
|
@ -202,6 +227,7 @@ V3OPTION_PARSER_DEF_OP(Set, VOptionBool*, ActionSet<VOptionBool>)
|
|||
#endif
|
||||
V3OPTION_PARSER_DEF_OP(Set, int*, ActionSet<int>)
|
||||
V3OPTION_PARSER_DEF_OP(Set, string*, ActionSet<string>)
|
||||
V3OPTION_PARSER_DEF_OP(FOnOff, bool*, ActionFOnOff<bool>)
|
||||
V3OPTION_PARSER_DEF_OP(OnOff, bool*, ActionOnOff<bool>)
|
||||
#ifndef V3OPTION_PARSER_NO_VOPTION_BOOL
|
||||
V3OPTION_PARSER_DEF_OP(OnOff, VOptionBool*, ActionOnOff<VOptionBool>)
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ private:
|
|||
// METHODS
|
||||
ActionIfs* find(const char* optp);
|
||||
template <class ACT, class ARG> ActionIfs& add(const string& opt, ARG arg);
|
||||
static bool hasPrefixFNo(const char* strp); // Returns true if strp starts with "-fno"
|
||||
static bool hasPrefixNo(const char* strp); // Returns true if strp starts with "-no"
|
||||
|
||||
public:
|
||||
|
|
@ -87,6 +88,7 @@ class V3OptionParser::ActionIfs VL_NOT_FINAL {
|
|||
public:
|
||||
virtual ~ActionIfs() = default;
|
||||
virtual bool isValueNeeded() const = 0; // Need val of "-opt val"
|
||||
virtual bool isFOnOffAllowed() const = 0; // true if "-fno-opt" is allowd
|
||||
virtual bool isOnOffAllowed() const = 0; // true if "-no-opt" is allowd
|
||||
virtual bool isPartialMatchAllowed() const = 0; // true if "-Wno-" matches "-Wno-fatal"
|
||||
virtual bool isUndocumented() const = 0; // Will not be suggested in typo
|
||||
|
|
@ -101,13 +103,15 @@ class V3OptionParser::AppendHelper final {
|
|||
public:
|
||||
// TYPES
|
||||
// Tag to specify which operator() to call
|
||||
struct Set {}; // For ActionSet
|
||||
struct FOnOff {}; // For ActionFOnOff
|
||||
struct OnOff {}; // For ActionOnOff
|
||||
struct Set {}; // For ActionSet
|
||||
|
||||
struct CbCall {}; // For ActionCbCall
|
||||
struct CbOnOff {}; // For ActionOnOff
|
||||
struct CbVal {}; // For ActionCbVal
|
||||
struct CbOnOff {}; // For ActionOnOff of ActionFOnOff
|
||||
struct CbPartialMatch {}; // For ActionCbPartialMatch
|
||||
struct CbPartialMatchVal {}; // For ActionCbPartialMatchVal
|
||||
struct CbVal {}; // For ActionCbVal
|
||||
|
||||
private:
|
||||
// MEMBERS
|
||||
|
|
@ -122,6 +126,7 @@ public:
|
|||
ActionIfs& operator()(const char* optp, Set, int*) const;
|
||||
ActionIfs& operator()(const char* optp, Set, string*) const;
|
||||
|
||||
ActionIfs& operator()(const char* optp, FOnOff, bool*) const;
|
||||
ActionIfs& operator()(const char* optp, OnOff, bool*) const;
|
||||
#ifndef V3OPTION_PARSER_NO_VOPTION_BOOL
|
||||
ActionIfs& operator()(const char* optp, OnOff, VOptionBool*) const;
|
||||
|
|
@ -144,13 +149,14 @@ public:
|
|||
|
||||
#define V3OPTION_PARSER_DECL_TAGS \
|
||||
const auto Set VL_ATTR_UNUSED = V3OptionParser::AppendHelper::Set{}; \
|
||||
const auto FOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::FOnOff{}; \
|
||||
const auto OnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::OnOff{}; \
|
||||
const auto CbCall VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbCall{}; \
|
||||
const auto CbOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbOnOff{}; \
|
||||
const auto CbVal VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbVal{}; \
|
||||
const auto CbPartialMatch VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbPartialMatch{}; \
|
||||
const auto CbPartialMatchVal VL_ATTR_UNUSED \
|
||||
= V3OptionParser::AppendHelper::CbPartialMatchVal {}
|
||||
= V3OptionParser::AppendHelper::CbPartialMatchVal{}; \
|
||||
const auto CbVal VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbVal{};
|
||||
|
||||
//######################################################################
|
||||
|
||||
|
|
|
|||
|
|
@ -775,8 +775,16 @@ void V3Options::notify() {
|
|||
&& !v3Global.opt.xmlOnly());
|
||||
}
|
||||
|
||||
// --trace-threads implies --threads 1 unless explicitly specified
|
||||
if (traceThreads() && !threads()) m_threads = 1;
|
||||
if (trace()) {
|
||||
// With --trace-fst, --trace-threads implies --threads 1 unless explicitly specified
|
||||
if (traceFormat().fst() && traceThreads() && !threads()) m_threads = 1;
|
||||
|
||||
// With --trace, --trace-threads is ignored
|
||||
if (traceFormat().vcd()) m_traceThreads = threads() ? 1 : 0;
|
||||
}
|
||||
|
||||
UASSERT(!(useTraceParallel() && useTraceOffload()),
|
||||
"Cannot use both parallel and offloaded tracing");
|
||||
|
||||
// Default split limits if not specified
|
||||
if (m_outputSplitCFuncs < 0) m_outputSplitCFuncs = m_outputSplit;
|
||||
|
|
@ -1075,6 +1083,29 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
|||
});
|
||||
DECL_OPTION("-flatten", OnOff, &m_flatten);
|
||||
|
||||
DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp);
|
||||
DECL_OPTION("-fassemble", FOnOff, &m_fAssemble);
|
||||
DECL_OPTION("-fcase", FOnOff, &m_fCase);
|
||||
DECL_OPTION("-fcombine", FOnOff, &m_fCombine);
|
||||
DECL_OPTION("-fconst", FOnOff, &m_fConst);
|
||||
DECL_OPTION("-fconst-bit-op-tree", FOnOff, &m_fConstBitOpTree);
|
||||
DECL_OPTION("-fdedup", FOnOff, &m_fDedupe);
|
||||
DECL_OPTION("-fexpand", FOnOff, &m_fExpand);
|
||||
DECL_OPTION("-fgate", FOnOff, &m_fGate);
|
||||
DECL_OPTION("-finline", FOnOff, &m_fInline);
|
||||
DECL_OPTION("-flife", FOnOff, &m_fLife);
|
||||
DECL_OPTION("-flife-post", FOnOff, &m_fLifePost);
|
||||
DECL_OPTION("-flocalize", FOnOff, &m_fLocalize);
|
||||
DECL_OPTION("-fmerge-cond", FOnOff, &m_fMergeCond);
|
||||
DECL_OPTION("-fmerge-cond-motion", FOnOff, &m_fMergeCondMotion);
|
||||
DECL_OPTION("-fmerge-const-pool", FOnOff, &m_fMergeConstPool);
|
||||
DECL_OPTION("-freloop", FOnOff, &m_fReloop);
|
||||
DECL_OPTION("-freorder", FOnOff, &m_fReorder);
|
||||
DECL_OPTION("-fsplit", FOnOff, &m_fSplit);
|
||||
DECL_OPTION("-fsubst", FOnOff, &m_fSubst);
|
||||
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
|
||||
DECL_OPTION("-ftable", FOnOff, &m_fTable);
|
||||
|
||||
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
|
||||
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);
|
||||
DECL_OPTION("-gdb", CbCall, []() {}); // Processed only in bin/verilator shell
|
||||
|
|
@ -1144,50 +1175,51 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
|||
}
|
||||
});
|
||||
DECL_OPTION("-max-num-width", Set, &m_maxNumWidth);
|
||||
DECL_OPTION("-merge-const-pool", OnOff, &m_mergeConstPool);
|
||||
DECL_OPTION("-mod-prefix", Set, &m_modPrefix);
|
||||
|
||||
DECL_OPTION("-O", CbPartialMatch, [this](const char* optp) {
|
||||
// Optimization
|
||||
DECL_OPTION("-O0", CbCall, [this]() { optimize(0); });
|
||||
DECL_OPTION("-O1", CbCall, [this]() { optimize(1); });
|
||||
DECL_OPTION("-O2", CbCall, [this]() { optimize(2); });
|
||||
DECL_OPTION("-O3", CbCall, [this]() { optimize(3); });
|
||||
|
||||
DECL_OPTION("-O", CbPartialMatch, [this, fl](const char* optp) {
|
||||
// Optimization, e.g. -O1rX
|
||||
// LCOV_EXCL_START
|
||||
fl->v3warn(DEPRECATED, "Option -O<letter> is deprecated. "
|
||||
"Use -f<optimization> or -fno-<optimization> instead.");
|
||||
for (const char* cp = optp; *cp; ++cp) {
|
||||
const bool flag = isupper(*cp);
|
||||
switch (tolower(*cp)) {
|
||||
case '0': optimize(0); break; // 0=all off
|
||||
case '1': optimize(1); break; // 1=all on
|
||||
case '2': optimize(2); break; // 2=not used
|
||||
case '3': optimize(3); break; // 3=high
|
||||
case 'a': m_oTable = flag; break;
|
||||
case 'b': m_oCombine = flag; break;
|
||||
case 'c': m_oConst = flag; break;
|
||||
case 'd': m_oDedupe = flag; break;
|
||||
case 'e': m_oCase = flag; break;
|
||||
// f
|
||||
case 'g': m_oGate = flag; break;
|
||||
// h
|
||||
case 'i': m_oInline = flag; break;
|
||||
// j
|
||||
case 'k': m_oSubstConst = flag; break;
|
||||
case 'l': m_oLife = flag; break;
|
||||
case 'm': m_oAssemble = flag; break;
|
||||
// n
|
||||
case 'o':
|
||||
m_oConstBitOpTree = flag;
|
||||
break; // Can remove ~2022-01 when stable
|
||||
// o will be used as an escape for a second character of optimization disables
|
||||
case '0': optimize(0); break;
|
||||
case '1': optimize(1); break;
|
||||
case '2': optimize(2); break;
|
||||
case '3': optimize(3); break;
|
||||
case 'a': m_fTable = flag; break; // == -fno-table
|
||||
case 'b': m_fCombine = flag; break; // == -fno-combine
|
||||
case 'c': m_fConst = flag; break; // == -fno-const
|
||||
case 'd': m_fDedupe = flag; break; // == -fno-dedup
|
||||
case 'e': m_fCase = flag; break; // == -fno-case
|
||||
case 'g': m_fGate = flag; break; // == -fno-gate
|
||||
case 'i': m_fInline = flag; break; // == -fno-inline
|
||||
case 'k': m_fSubstConst = flag; break; // == -fno-subst-const
|
||||
case 'l': m_fLife = flag; break; // == -fno-life
|
||||
case 'm': m_fAssemble = flag; break; // == -fno-assemble
|
||||
case 'o': m_fConstBitOpTree = flag; break; // == -fno-const-bit-op-tree
|
||||
case 'p':
|
||||
m_public = !flag;
|
||||
break; // With -Op so flag=0, we want public on so few optimizations done
|
||||
// q
|
||||
case 'r': m_oReorder = flag; break;
|
||||
case 's': m_oSplit = flag; break;
|
||||
case 't': m_oLifePost = flag; break;
|
||||
case 'u': m_oSubst = flag; break;
|
||||
case 'v': m_oReloop = flag; break;
|
||||
case 'w': m_oMergeCond = flag; break;
|
||||
case 'x': m_oExpand = flag; break;
|
||||
case 'y': m_oAcycSimp = flag; break;
|
||||
case 'z': m_oLocalize = flag; break;
|
||||
default: break; // No error, just ignore
|
||||
case 'r': m_fReorder = flag; break; // == -fno-reorder
|
||||
case 's': m_fSplit = flag; break; // == -fno-split
|
||||
case 't': m_fLifePost = flag; break; // == -fno-life-post
|
||||
case 'u': m_fSubst = flag; break; // == -fno-subst
|
||||
case 'v': m_fReloop = flag; break; // == -fno-reloop
|
||||
case 'w': m_fMergeCond = flag; break; // == -fno-merge-cond
|
||||
case 'x': m_fExpand = flag; break; // == -fno-expand
|
||||
case 'y': m_fAcycSimp = flag; break; // == -fno-acyc-simp
|
||||
case 'z': m_fLocalize = flag; break; // == -fno-localize
|
||||
default:
|
||||
break; // No error, just ignore
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1350,7 +1382,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
|||
DECL_OPTION("-trace-threads", CbVal, [this, fl](const char* valp) {
|
||||
m_trace = true;
|
||||
m_traceThreads = std::atoi(valp);
|
||||
if (m_traceThreads < 0) fl->v3fatal("--trace-threads must be >= 0: " << valp);
|
||||
if (m_traceThreads < 1) fl->v3fatal("--trace-threads must be >= 1: " << valp);
|
||||
});
|
||||
DECL_OPTION("-trace-underscore", OnOff, &m_traceUnderscore);
|
||||
|
||||
|
|
@ -1779,26 +1811,26 @@ int V3Options::dumpTreeLevel(const string& srcfile_path) {
|
|||
void V3Options::optimize(int level) {
|
||||
// Set all optimizations to on/off
|
||||
const bool flag = level > 0;
|
||||
m_oAcycSimp = flag;
|
||||
m_oAssemble = flag;
|
||||
m_oCase = flag;
|
||||
m_oCombine = flag;
|
||||
m_oConst = flag;
|
||||
m_oConstBitOpTree = flag;
|
||||
m_oDedupe = flag;
|
||||
m_oExpand = flag;
|
||||
m_oGate = flag;
|
||||
m_oInline = flag;
|
||||
m_oLife = flag;
|
||||
m_oLifePost = flag;
|
||||
m_oLocalize = flag;
|
||||
m_oMergeCond = flag;
|
||||
m_oReloop = flag;
|
||||
m_oReorder = flag;
|
||||
m_oSplit = flag;
|
||||
m_oSubst = flag;
|
||||
m_oSubstConst = flag;
|
||||
m_oTable = flag;
|
||||
m_fAcycSimp = flag;
|
||||
m_fAssemble = flag;
|
||||
m_fCase = flag;
|
||||
m_fCombine = flag;
|
||||
m_fConst = flag;
|
||||
m_fConstBitOpTree = flag;
|
||||
m_fDedupe = flag;
|
||||
m_fExpand = flag;
|
||||
m_fGate = flag;
|
||||
m_fInline = flag;
|
||||
m_fLife = flag;
|
||||
m_fLifePost = flag;
|
||||
m_fLocalize = flag;
|
||||
m_fMergeCond = flag;
|
||||
m_fReloop = flag;
|
||||
m_fReorder = flag;
|
||||
m_fSplit = flag;
|
||||
m_fSubst = flag;
|
||||
m_fSubstConst = flag;
|
||||
m_fTable = flag;
|
||||
// And set specific optimization levels
|
||||
if (level >= 3) {
|
||||
m_inlineMult = -1; // Maximum inlining
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ public:
|
|||
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
|
||||
operator en() const { return m_e; }
|
||||
bool fst() const { return m_e == FST; }
|
||||
bool vcd() const { return m_e == VCD; }
|
||||
string classBase() const {
|
||||
static const char* const names[] = {"VerilatedVcd", "VerilatedFst"};
|
||||
return names[m_e];
|
||||
|
|
@ -245,7 +246,6 @@ private:
|
|||
bool m_lintOnly = false; // main switch: --lint-only
|
||||
bool m_gmake = false; // main switch: --make gmake
|
||||
bool m_main = false; // main swithc: --main
|
||||
bool m_mergeConstPool = true; // main switch: --merge-const-pool
|
||||
bool m_orderClockDly = true; // main switch: --order-clock-delay
|
||||
bool m_outFormatOk = false; // main switch: --cc, --sc or --sp was specified
|
||||
bool m_pedantic = false; // main switch: --Wpedantic
|
||||
|
|
@ -340,27 +340,28 @@ private:
|
|||
V3LangCode m_defaultLanguage; // main switch: --language
|
||||
|
||||
// MEMBERS (optimizations)
|
||||
// // main switch: -Op: --public
|
||||
bool m_oAcycSimp; // main switch: -Oy: acyclic pre-optimizations
|
||||
bool m_oAssemble; // main switch: -Om: assign assemble
|
||||
bool m_oCase; // main switch: -Oe: case tree conversion
|
||||
bool m_oCombine; // main switch: -Ob: common icode packing
|
||||
bool m_oConst; // main switch: -Oc: constant folding
|
||||
bool m_oConstBitOpTree; // main switch: -Oo: constant bit op tree
|
||||
bool m_oDedupe; // main switch: -Od: logic deduplication
|
||||
bool m_oExpand; // main switch: -Ox: expansion of C macros
|
||||
bool m_oGate; // main switch: -Og: gate wire elimination
|
||||
bool m_oInline; // main switch: -Oi: module inlining
|
||||
bool m_oLife; // main switch: -Ol: variable lifetime
|
||||
bool m_oLifePost; // main switch: -Ot: delayed assignment elimination
|
||||
bool m_oLocalize; // main switch: -Oz: convert temps to local variables
|
||||
bool m_oMergeCond; // main switch: -Ob: merge conditionals
|
||||
bool m_oReloop; // main switch: -Ov: reform loops
|
||||
bool m_oReorder; // main switch: -Or: reorder assignments in blocks
|
||||
bool m_oSplit; // main switch: -Os: always assignment splitting
|
||||
bool m_oSubst; // main switch: -Ou: substitute expression temp values
|
||||
bool m_oSubstConst; // main switch: -Ok: final constant substitution
|
||||
bool m_oTable; // main switch: -Oa: lookup table creation
|
||||
bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations
|
||||
bool m_fAssemble; // main switch: -fno-assemble: assign assemble
|
||||
bool m_fCase; // main switch: -fno-case: case tree conversion
|
||||
bool m_fCombine; // main switch: -fno-combine: common icode packing
|
||||
bool m_fConst; // main switch: -fno-const: constant folding
|
||||
bool m_fConstBitOpTree; // main switch: -fno-const-bit-op-tree constant bit op tree
|
||||
bool m_fDedupe; // main switch: -fno-dedupe: logic deduplication
|
||||
bool m_fExpand; // main switch: -fno-expand: expansion of C macros
|
||||
bool m_fGate; // main switch: -fno-gate: gate wire elimination
|
||||
bool m_fInline; // main switch: -fno-inline: module inlining
|
||||
bool m_fLife; // main switch: -fno-life: variable lifetime
|
||||
bool m_fLifePost; // main switch: -fno-life-post: delayed assignment elimination
|
||||
bool m_fLocalize; // main switch: -fno-localize: convert temps to local variables
|
||||
bool m_fMergeCond; // main switch: -fno-merge-cond: merge conditionals
|
||||
bool m_fMergeCondMotion = true; // main switch: -fno-merge-cond-motion: perform code motion
|
||||
bool m_fMergeConstPool = true; // main switch: -fno-merge-const-pool
|
||||
bool m_fReloop; // main switch: -fno-reloop: reform loops
|
||||
bool m_fReorder; // main switch: -fno-reorder: reorder assignments in blocks
|
||||
bool m_fSplit; // main switch: -fno-split: always assignment splitting
|
||||
bool m_fSubst; // main switch: -fno-subst: substitute expression temp values
|
||||
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
|
||||
bool m_fTable; // main switch: -fno-table: lookup table creation
|
||||
// clang-format on
|
||||
|
||||
bool m_available = false; // Set to true at the end of option parsing
|
||||
|
|
@ -458,7 +459,6 @@ public:
|
|||
bool traceStructs() const { return m_traceStructs; }
|
||||
bool traceUnderscore() const { return m_traceUnderscore; }
|
||||
bool main() const { return m_main; }
|
||||
bool mergeConstPool() const { return m_mergeConstPool; }
|
||||
bool orderClockDly() const { return m_orderClockDly; }
|
||||
bool outFormatOk() const { return m_outFormatOk; }
|
||||
bool keepTempFiles() const { return (V3Error::debugDefault() != 0); }
|
||||
|
|
@ -517,8 +517,10 @@ public:
|
|||
int traceMaxArray() const { return m_traceMaxArray; }
|
||||
int traceMaxWidth() const { return m_traceMaxWidth; }
|
||||
int traceThreads() const { return m_traceThreads; }
|
||||
bool trueTraceThreads() const {
|
||||
return traceThreads() == 0 ? 0 : traceThreads() - traceFormat().fst();
|
||||
bool useTraceOffload() const { return trace() && traceFormat().fst() && traceThreads() > 1; }
|
||||
bool useTraceParallel() const { return trace() && traceFormat().vcd() && threads() > 1; }
|
||||
unsigned vmTraceThreads() const {
|
||||
return useTraceParallel() ? threads() : useTraceOffload() ? 1 : 0;
|
||||
}
|
||||
int unrollCount() const { return m_unrollCount; }
|
||||
int unrollStmts() const { return m_unrollStmts; }
|
||||
|
|
@ -572,26 +574,28 @@ public:
|
|||
bool isNoClocker(const string& signame) const;
|
||||
|
||||
// ACCESSORS (optimization options)
|
||||
bool oAcycSimp() const { return m_oAcycSimp; }
|
||||
bool oAssemble() const { return m_oAssemble; }
|
||||
bool oCase() const { return m_oCase; }
|
||||
bool oCombine() const { return m_oCombine; }
|
||||
bool oConst() const { return m_oConst; }
|
||||
bool oConstBitOpTree() const { return m_oConstBitOpTree; }
|
||||
bool oDedupe() const { return m_oDedupe; }
|
||||
bool oExpand() const { return m_oExpand; }
|
||||
bool oGate() const { return m_oGate; }
|
||||
bool oInline() const { return m_oInline; }
|
||||
bool oLife() const { return m_oLife; }
|
||||
bool oLifePost() const { return m_oLifePost; }
|
||||
bool oLocalize() const { return m_oLocalize; }
|
||||
bool oMergeCond() const { return m_oMergeCond; }
|
||||
bool oReloop() const { return m_oReloop; }
|
||||
bool oReorder() const { return m_oReorder; }
|
||||
bool oSplit() const { return m_oSplit; }
|
||||
bool oSubst() const { return m_oSubst; }
|
||||
bool oSubstConst() const { return m_oSubstConst; }
|
||||
bool oTable() const { return m_oTable; }
|
||||
bool fAcycSimp() const { return m_fAcycSimp; }
|
||||
bool fAssemble() const { return m_fAssemble; }
|
||||
bool fCase() const { return m_fCase; }
|
||||
bool fCombine() const { return m_fCombine; }
|
||||
bool fConst() const { return m_fConst; }
|
||||
bool fConstBitOpTree() const { return m_fConstBitOpTree; }
|
||||
bool fDedupe() const { return m_fDedupe; }
|
||||
bool fExpand() const { return m_fExpand; }
|
||||
bool fGate() const { return m_fGate; }
|
||||
bool fInline() const { return m_fInline; }
|
||||
bool fLife() const { return m_fLife; }
|
||||
bool fLifePost() const { return m_fLifePost; }
|
||||
bool fLocalize() const { return m_fLocalize; }
|
||||
bool fMergeCond() const { return m_fMergeCond; }
|
||||
bool fMergeCondMotion() const { return m_fMergeCondMotion; }
|
||||
bool fMergeConstPool() const { return m_fMergeConstPool; }
|
||||
bool fReloop() const { return m_fReloop; }
|
||||
bool fReorder() const { return m_fReorder; }
|
||||
bool fSplit() const { return m_fSplit; }
|
||||
bool fSubst() const { return m_fSubst; }
|
||||
bool fSubstConst() const { return m_fSubstConst; }
|
||||
bool fTable() const { return m_fTable; }
|
||||
|
||||
string traceClassBase() const { return m_traceFormat.classBase(); }
|
||||
string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); }
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,10 @@ class OrderProcess final : VNDeleter {
|
|||
return name;
|
||||
}
|
||||
|
||||
bool nodeIsInitial(const OrderLogicVertex* LVtxp) {
|
||||
return LVtxp && (VN_IS(LVtxp->nodep(), Initial) || VN_IS(LVtxp->nodep(), InitialStatic));
|
||||
}
|
||||
|
||||
void nodeMarkCircular(OrderVarVertex* vertexp, OrderEdge* edgep) {
|
||||
// To be marked circular requires being a clock assigned in a delayed assignment, or
|
||||
// having a cutable in or out edge, none of which is true for the DPI export trigger.
|
||||
|
|
@ -1146,8 +1150,7 @@ class OrderProcess final : VNDeleter {
|
|||
toLVtxp = dynamic_cast<OrderLogicVertex*>(edgep->top());
|
||||
}
|
||||
//
|
||||
if ((fromLVtxp && VN_IS(fromLVtxp->nodep(), Initial))
|
||||
|| (toLVtxp && VN_IS(toLVtxp->nodep(), Initial))) {
|
||||
if (nodeIsInitial(fromLVtxp) || nodeIsInitial(toLVtxp)) {
|
||||
// IEEE does not specify ordering between initial blocks, so we
|
||||
// can do whatever we want. We especially do not want to
|
||||
// evaluate multiple times, so do not mark the edge circular
|
||||
|
|
|
|||
|
|
@ -189,6 +189,8 @@ AstVar* V3ParseGrammar::createVariable(FileLine* fileline, const string& name,
|
|||
nodep->ansi(m_pinAnsi);
|
||||
nodep->declTyped(m_varDeclTyped);
|
||||
nodep->lifetime(m_varLifetime);
|
||||
nodep->delayp(m_netDelayp);
|
||||
m_netDelayp = nullptr;
|
||||
if (GRAMMARP->m_varDecl != VVarType::UNKNOWN) nodep->combineType(GRAMMARP->m_varDecl);
|
||||
if (GRAMMARP->m_varIO != VDirection::NONE) {
|
||||
nodep->declDirection(GRAMMARP->m_varIO);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ private:
|
|||
&& !constp->num().isString(); // Not a string
|
||||
if (useConstPool) {
|
||||
// Extract into constant pool.
|
||||
const bool merge = v3Global.opt.mergeConstPool();
|
||||
const bool merge = v3Global.opt.fMergeConstPool();
|
||||
varp = v3Global.rootp()->constPoolp()->findConst(constp, merge)->varp();
|
||||
nodep->deleteTree();
|
||||
++m_extractedToConstPool;
|
||||
|
|
|
|||
|
|
@ -180,6 +180,10 @@ private:
|
|||
TraceActivityVertex* const m_alwaysVtxp; // "Always trace" vertex
|
||||
bool m_finding = false; // Pass one of algorithm?
|
||||
|
||||
// Trace parallelism. Only VCD tracing can be parallelized at this time.
|
||||
const uint32_t m_parallelism
|
||||
= v3Global.opt.useTraceParallel() ? static_cast<uint32_t>(v3Global.opt.threads()) : 1;
|
||||
|
||||
VDouble0 m_statUniqSigs; // Statistic tracking
|
||||
VDouble0 m_statUniqCodes; // Statistic tracking
|
||||
|
||||
|
|
@ -388,7 +392,7 @@ private:
|
|||
if (!it->second->duplicatep()) {
|
||||
uint32_t cost = 0;
|
||||
const AstTraceDecl* const declp = it->second->nodep();
|
||||
// The number of comparisons required by tracep->chg*
|
||||
// The number of comparisons required by bufp->chg*
|
||||
cost += declp->isWide() ? declp->codeInc() : 1;
|
||||
// Arrays are traced by element
|
||||
cost *= declp->arrayRange().ranged() ? declp->arrayRange().elements() : 1;
|
||||
|
|
@ -494,7 +498,7 @@ private:
|
|||
};
|
||||
if (isTopFunc) {
|
||||
// Top functions
|
||||
funcp->argTypes("void* voidSelf, " + v3Global.opt.traceClassBase() + "* tracep");
|
||||
funcp->argTypes("void* voidSelf, " + v3Global.opt.traceClassBase() + "::Buffer* bufp");
|
||||
addInitStr(voidSelfAssign(m_topModp));
|
||||
addInitStr(symClassAssign());
|
||||
// Add global activity check to change dump functions
|
||||
|
|
@ -508,32 +512,33 @@ private:
|
|||
m_regFuncp->addStmtsp(new AstText(flp, "tracep->addChgCb(", true));
|
||||
}
|
||||
m_regFuncp->addStmtsp(new AstAddrOfCFunc(flp, funcp));
|
||||
m_regFuncp->addStmtsp(new AstText(flp, ", vlSelf);\n", true));
|
||||
const string threadPool{m_parallelism > 1 ? "vlSymsp->__Vm_threadPoolp" : "nullptr"};
|
||||
m_regFuncp->addStmtsp(new AstText(flp, ", vlSelf, " + threadPool + ");\n", true));
|
||||
} else {
|
||||
// Sub functions
|
||||
funcp->argTypes(v3Global.opt.traceClassBase() + "* tracep");
|
||||
funcp->argTypes(v3Global.opt.traceClassBase() + "::Buffer* bufp");
|
||||
// Setup base references. Note in rare occasions we can end up with an empty trace
|
||||
// sub function, hence the VL_ATTR_UNUSED attributes.
|
||||
if (full) {
|
||||
// Full dump sub function
|
||||
addInitStr("uint32_t* const oldp VL_ATTR_UNUSED = "
|
||||
"tracep->oldp(vlSymsp->__Vm_baseCode);\n");
|
||||
"bufp->oldp(vlSymsp->__Vm_baseCode);\n");
|
||||
} else {
|
||||
// Change dump sub function
|
||||
if (v3Global.opt.trueTraceThreads()) {
|
||||
if (v3Global.opt.useTraceOffload()) {
|
||||
addInitStr("const uint32_t base VL_ATTR_UNUSED = "
|
||||
"vlSymsp->__Vm_baseCode + "
|
||||
+ cvtToStr(baseCode) + ";\n");
|
||||
addInitStr("if (false && tracep) {} // Prevent unused\n");
|
||||
addInitStr("if (false && bufp) {} // Prevent unused\n");
|
||||
} else {
|
||||
addInitStr("uint32_t* const oldp VL_ATTR_UNUSED = "
|
||||
"tracep->oldp(vlSymsp->__Vm_baseCode + "
|
||||
"bufp->oldp(vlSymsp->__Vm_baseCode + "
|
||||
+ cvtToStr(baseCode) + ");\n");
|
||||
}
|
||||
}
|
||||
// Add call to top function
|
||||
AstCCall* const callp = new AstCCall(funcp->fileline(), funcp);
|
||||
callp->argTypes("tracep");
|
||||
callp->argTypes("bufp");
|
||||
topFuncp->addStmtsp(callp);
|
||||
}
|
||||
// Done
|
||||
|
|
@ -728,7 +733,7 @@ private:
|
|||
// We will split functions such that each have to dump roughly the same amount of data
|
||||
// for this we need to keep tack of the number of codes used by the trace functions.
|
||||
uint32_t nFullCodes = 0; // Number of non-duplicate codes (need to go into full* dump)
|
||||
uint32_t nChgCodes = 0; // Number of non-consant codes (need to go in to chg* dump)
|
||||
uint32_t nChgCodes = 0; // Number of non-constant codes (need to go in to chg* dump)
|
||||
sortTraces(traces, nFullCodes, nChgCodes);
|
||||
|
||||
UINFO(5, "nFullCodes: " << nFullCodes << " nChgCodes: " << nChgCodes << endl);
|
||||
|
|
@ -747,13 +752,11 @@ private:
|
|||
m_regFuncp->isLoose(true);
|
||||
m_topScopep->addActivep(m_regFuncp);
|
||||
|
||||
const int parallelism = 1; // Note: will bump this later, code below works for any value
|
||||
|
||||
// Create the full dump functions, also allocates signal numbers
|
||||
createFullTraceFunction(traces, nFullCodes, parallelism);
|
||||
createFullTraceFunction(traces, nFullCodes, m_parallelism);
|
||||
|
||||
// Create the incremental dump functions
|
||||
createChgTraceFunctions(traces, nChgCodes, parallelism);
|
||||
createChgTraceFunctions(traces, nChgCodes, m_parallelism);
|
||||
|
||||
// Remove refs to traced values from TraceDecl nodes, these have now moved under
|
||||
// TraceInc
|
||||
|
|
|
|||
|
|
@ -25,9 +25,10 @@
|
|||
#include "verilated_trace_defs.h" // For VLT_TRACE_SCOPE_*
|
||||
|
||||
#include "V3Global.h"
|
||||
#include "V3TraceDecl.h"
|
||||
#include "V3Config.h"
|
||||
#include "V3EmitCBase.h"
|
||||
#include "V3Stats.h"
|
||||
#include "V3TraceDecl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
|
@ -140,10 +141,13 @@ private:
|
|||
return "Verilator trace_off";
|
||||
} else if (!nodep->isTrace()) {
|
||||
return "Verilator instance trace_off";
|
||||
} else if (!v3Global.opt.traceUnderscore()) {
|
||||
} else {
|
||||
const string prettyName = varp->prettyName();
|
||||
if (!prettyName.empty() && prettyName[0] == '_') return "Leading underscore";
|
||||
if (prettyName.find("._") != string::npos) return "Inlined leading underscore";
|
||||
if (!v3Global.opt.traceUnderscore()) {
|
||||
if (!prettyName.empty() && prettyName[0] == '_') return "Leading underscore";
|
||||
if (prettyName.find("._") != string::npos) return "Inlined leading underscore";
|
||||
}
|
||||
if (!V3Config::getScopeTraceOn(prettyName)) return "Vlt scope trace_off";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -504,6 +504,7 @@ private:
|
|||
// width: LHS + RHS
|
||||
AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp();
|
||||
userIterate(vdtypep, WidthVP(SELF, BOTH).p());
|
||||
// Conversions
|
||||
if (VN_IS(vdtypep, QueueDType)) {
|
||||
// Queue "element 0" is lhsp, so we need to swap arguments
|
||||
auto* const newp = new AstConsQueue(nodep->fileline(), nodep->rhsp()->unlinkFrBack(),
|
||||
|
|
@ -521,6 +522,16 @@ private:
|
|||
userIterateChildren(newp, m_vup);
|
||||
return;
|
||||
}
|
||||
if (VN_IS(vdtypep, UnpackArrayDType)) {
|
||||
auto* const newp = new AstPattern{nodep->fileline(), nullptr};
|
||||
patConcatConvertRecurse(newp, nodep);
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
userIterate(newp, m_vup);
|
||||
return;
|
||||
}
|
||||
|
||||
// Concat handling
|
||||
if (m_vup->prelim()) {
|
||||
if (VN_IS(vdtypep, AssocArrayDType) //
|
||||
|| VN_IS(vdtypep, DynArrayDType) //
|
||||
|
|
@ -595,6 +606,7 @@ private:
|
|||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
return;
|
||||
}
|
||||
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack());
|
||||
nodep->v3warn(STMTDLY, "Unsupported: Ignoring delay on this delayed statement.");
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
}
|
||||
|
|
@ -661,7 +673,8 @@ private:
|
|||
}
|
||||
|
||||
AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp();
|
||||
if (VN_IS(vdtypep, QueueDType) || VN_IS(vdtypep, DynArrayDType)) {
|
||||
if (VN_IS(vdtypep, QueueDType) || VN_IS(vdtypep, DynArrayDType)
|
||||
|| VN_IS(vdtypep, UnpackArrayDType)) {
|
||||
if (times != 1)
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Non-1 replication to form "
|
||||
<< vdtypep->prettyDTypeNameQ()
|
||||
|
|
@ -673,7 +686,7 @@ private:
|
|||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
return;
|
||||
}
|
||||
if (VN_IS(vdtypep, AssocArrayDType) || VN_IS(vdtypep, UnpackArrayDType)) {
|
||||
if (VN_IS(vdtypep, AssocArrayDType)) {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Replication to form "
|
||||
<< vdtypep->prettyDTypeNameQ() << " data type");
|
||||
}
|
||||
|
|
@ -1323,10 +1336,10 @@ private:
|
|||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstTimingControl* nodep) override {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: timing control statement in this location\n"
|
||||
virtual void visit(AstEventControl* nodep) override {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: event control statement in this location\n"
|
||||
<< nodep->warnMore()
|
||||
<< "... Suggest have one timing control statement "
|
||||
<< "... Suggest have one event control statement "
|
||||
<< "per procedure, at the top of the procedure");
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
}
|
||||
|
|
@ -2725,7 +2738,7 @@ private:
|
|||
methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ);
|
||||
newp = new AstCMethodHard(nodep->fileline(), nodep->fromp()->unlinkFrBack(),
|
||||
"r_" + nodep->name(), withp);
|
||||
newp->dtypeFrom(adtypep->subDTypep());
|
||||
newp->dtypeFrom(withp ? withp->dtypep() : adtypep->subDTypep());
|
||||
if (!nodep->firstAbovep()) newp->makeStatement();
|
||||
} else if (nodep->name() == "min" || nodep->name() == "max" || nodep->name() == "unique"
|
||||
|| nodep->name() == "unique_index") {
|
||||
|
|
@ -2949,7 +2962,7 @@ private:
|
|||
methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ);
|
||||
newp = new AstCMethodHard(nodep->fileline(), nodep->fromp()->unlinkFrBack(),
|
||||
"r_" + nodep->name(), withp);
|
||||
newp->dtypeFrom(adtypep->subDTypep());
|
||||
newp->dtypeFrom(withp ? withp->dtypep() : adtypep->subDTypep());
|
||||
if (!nodep->firstAbovep()) newp->makeStatement();
|
||||
} else if (nodep->name() == "reverse" || nodep->name() == "shuffle"
|
||||
|| nodep->name() == "sort" || nodep->name() == "rsort") {
|
||||
|
|
@ -3984,6 +3997,11 @@ private:
|
|||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type");
|
||||
}
|
||||
}
|
||||
if (nodep->timingControlp()) {
|
||||
nodep->timingControlp()->v3warn(
|
||||
ASSIGNDLY, "Unsupported: Ignoring timing control on this assignment.");
|
||||
nodep->timingControlp()->unlinkFrBackWithNext()->deleteTree();
|
||||
}
|
||||
if (VN_IS(nodep->rhsp(), EmptyQueue)) {
|
||||
UINFO(9, "= {} -> .delete(): " << nodep);
|
||||
if (!VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
|
||||
|
|
@ -6225,6 +6243,21 @@ private:
|
|||
return patmap;
|
||||
}
|
||||
|
||||
void patConcatConvertRecurse(AstPattern* patternp, AstConcat* nodep) {
|
||||
if (AstConcat* lhsp = VN_CAST(nodep->lhsp(), Concat)) {
|
||||
patConcatConvertRecurse(patternp, lhsp);
|
||||
} else {
|
||||
patternp->addItemsp(new AstPatMember{nodep->lhsp()->fileline(),
|
||||
nodep->lhsp()->unlinkFrBack(), nullptr, nullptr});
|
||||
}
|
||||
if (AstConcat* rhsp = VN_CAST(nodep->rhsp(), Concat)) {
|
||||
patConcatConvertRecurse(patternp, rhsp);
|
||||
} else {
|
||||
patternp->addItemsp(new AstPatMember{nodep->rhsp()->fileline(),
|
||||
nodep->rhsp()->unlinkFrBack(), nullptr, nullptr});
|
||||
}
|
||||
}
|
||||
|
||||
void makeOpenArrayShell(AstNodeFTaskRef* nodep) {
|
||||
UINFO(4, "Replicate openarray function " << nodep->taskp() << endl);
|
||||
AstNodeFTask* const oldTaskp = nodep->taskp();
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ static void process() {
|
|||
// Module inlining
|
||||
// Cannot remove dead variables after this, as alias information for final
|
||||
// V3Scope's V3LinkDot is in the AstVar.
|
||||
if (v3Global.opt.oInline()) {
|
||||
if (v3Global.opt.fInline()) {
|
||||
V3Inline::inlineAll(v3Global.rootp());
|
||||
V3LinkDot::linkDotArrayed(v3Global.rootp()); // Cleanup as made new modules
|
||||
}
|
||||
|
|
@ -310,11 +310,11 @@ static void process() {
|
|||
// Push constants across variables and remove redundant assignments
|
||||
V3Const::constifyAll(v3Global.rootp());
|
||||
|
||||
if (v3Global.opt.oLife()) V3Life::lifeAll(v3Global.rootp());
|
||||
if (v3Global.opt.fLife()) V3Life::lifeAll(v3Global.rootp());
|
||||
|
||||
// Make large low-fanin logic blocks into lookup tables
|
||||
// This should probably be done much later, once we have common logic elimination.
|
||||
if (!v3Global.opt.lintOnly() && v3Global.opt.oTable()) {
|
||||
if (!v3Global.opt.lintOnly() && v3Global.opt.fTable()) {
|
||||
V3Table::tableAll(v3Global.rootp());
|
||||
}
|
||||
|
||||
|
|
@ -328,7 +328,7 @@ static void process() {
|
|||
V3Active::activeAll(v3Global.rootp());
|
||||
|
||||
// Split single ALWAYS blocks into multiple blocks for better ordering chances
|
||||
if (v3Global.opt.oSplit()) V3Split::splitAlwaysAll(v3Global.rootp());
|
||||
if (v3Global.opt.fSplit()) V3Split::splitAlwaysAll(v3Global.rootp());
|
||||
V3SplitAs::splitAsAll(v3Global.rootp());
|
||||
|
||||
// Create tracing sample points, before we start eliminating signals
|
||||
|
|
@ -340,11 +340,11 @@ static void process() {
|
|||
|
||||
// Gate-based logic elimination; eliminate signals and push constant across cell boundaries
|
||||
// Instant propagation makes lots-o-constant reduction possibilities.
|
||||
if (v3Global.opt.oGate()) {
|
||||
if (v3Global.opt.fGate()) {
|
||||
V3Gate::gateAll(v3Global.rootp());
|
||||
// V3Gate calls constant propagation itself.
|
||||
} else {
|
||||
v3info("Command Line disabled gate optimization with -Og/-O0. "
|
||||
v3info("Command Line disabled gate optimization with -fno-gate. "
|
||||
"This may cause ordering problems.");
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +363,7 @@ static void process() {
|
|||
}
|
||||
|
||||
// Reorder assignments in pipelined blocks
|
||||
if (v3Global.opt.oReorder()) V3Split::splitReorderAll(v3Global.rootp());
|
||||
if (v3Global.opt.fReorder()) V3Split::splitReorderAll(v3Global.rootp());
|
||||
|
||||
// Create delayed assignments
|
||||
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step
|
||||
|
|
@ -388,11 +388,11 @@ static void process() {
|
|||
// Cleanup any dly vars or other temps that are simple assignments
|
||||
// Life must be done before Subst, as it assumes each CFunc under
|
||||
// _eval is called only once.
|
||||
if (v3Global.opt.oLife()) {
|
||||
if (v3Global.opt.fLife()) {
|
||||
V3Const::constifyAll(v3Global.rootp());
|
||||
V3Life::lifeAll(v3Global.rootp());
|
||||
}
|
||||
if (v3Global.opt.oLifePost()) V3LifePost::lifepostAll(v3Global.rootp());
|
||||
if (v3Global.opt.fLifePost()) V3LifePost::lifepostAll(v3Global.rootp());
|
||||
|
||||
// Remove unused vars
|
||||
V3Const::constifyAll(v3Global.rootp());
|
||||
|
|
@ -422,13 +422,13 @@ static void process() {
|
|||
v3Global.assertScoped(false);
|
||||
|
||||
// Move variables from modules to function local variables where possible
|
||||
if (v3Global.opt.oLocalize()) V3Localize::localizeAll(v3Global.rootp());
|
||||
if (v3Global.opt.fLocalize()) V3Localize::localizeAll(v3Global.rootp());
|
||||
|
||||
// Remove remaining scopes; make varrefs/funccalls relative to current module
|
||||
V3Descope::descopeAll(v3Global.rootp());
|
||||
|
||||
// Icache packing; combine common code in each module's functions into subroutines
|
||||
if (v3Global.opt.oCombine()) V3Combine::combineAll(v3Global.rootp());
|
||||
if (v3Global.opt.fCombine()) V3Combine::combineAll(v3Global.rootp());
|
||||
}
|
||||
|
||||
V3Error::abortIfErrors();
|
||||
|
|
@ -452,30 +452,30 @@ static void process() {
|
|||
}
|
||||
|
||||
// Expand macros and wide operators into C++ primitives
|
||||
if (!v3Global.opt.lintOnly() && !v3Global.opt.xmlOnly() && v3Global.opt.oExpand()) {
|
||||
if (!v3Global.opt.lintOnly() && !v3Global.opt.xmlOnly() && v3Global.opt.fExpand()) {
|
||||
V3Expand::expandAll(v3Global.rootp());
|
||||
}
|
||||
|
||||
// Propagate constants across WORDSEL arrayed temporaries
|
||||
if (!v3Global.opt.xmlOnly() && v3Global.opt.oSubst()) {
|
||||
if (!v3Global.opt.xmlOnly() && v3Global.opt.fSubst()) {
|
||||
// Constant folding of expanded stuff
|
||||
V3Const::constifyCpp(v3Global.rootp());
|
||||
V3Subst::substituteAll(v3Global.rootp());
|
||||
}
|
||||
|
||||
if (!v3Global.opt.xmlOnly() && v3Global.opt.oSubstConst()) {
|
||||
if (!v3Global.opt.xmlOnly() && v3Global.opt.fSubstConst()) {
|
||||
// Constant folding of substitutions
|
||||
V3Const::constifyCpp(v3Global.rootp());
|
||||
V3Dead::deadifyAll(v3Global.rootp());
|
||||
}
|
||||
|
||||
if (!v3Global.opt.lintOnly() && !v3Global.opt.xmlOnly()) {
|
||||
if (v3Global.opt.oMergeCond()) {
|
||||
if (v3Global.opt.fMergeCond()) {
|
||||
// Merge conditionals
|
||||
V3MergeCond::mergeAll(v3Global.rootp());
|
||||
}
|
||||
|
||||
if (v3Global.opt.oReloop()) {
|
||||
if (v3Global.opt.fReloop()) {
|
||||
// Reform loops to reduce code size
|
||||
// Must be after all Sel/array index based optimizations
|
||||
V3Reloop::reloopAll(v3Global.rootp());
|
||||
|
|
|
|||
|
|
@ -138,12 +138,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
|
|||
-?"-cost" { FL; return yVLT_D_COST; }
|
||||
-?"-file" { FL; return yVLT_D_FILE; }
|
||||
-?"-function" { FL; return yVLT_D_FUNCTION; }
|
||||
-?"-levels" { FL; return yVLT_D_LEVELS; }
|
||||
-?"-lines" { FL; return yVLT_D_LINES; }
|
||||
-?"-match" { FL; return yVLT_D_MATCH; }
|
||||
-?"-model" { FL; return yVLT_D_MODEL; }
|
||||
-?"-module" { FL; return yVLT_D_MODULE; }
|
||||
-?"-mtask" { FL; return yVLT_D_MTASK; }
|
||||
-?"-rule" { FL; return yVLT_D_RULE; }
|
||||
-?"-scope" { FL; return yVLT_D_SCOPE; }
|
||||
-?"-task" { FL; return yVLT_D_TASK; }
|
||||
-?"-var" { FL; return yVLT_D_VAR; }
|
||||
|
||||
|
|
|
|||
9047
src/verilog.y
9047
src/verilog.y
File diff suppressed because it is too large
Load Diff
|
|
@ -77,7 +77,6 @@ my $opt_gdbbt;
|
|||
my $opt_gdbsim;
|
||||
my $opt_hashset;
|
||||
my $opt_jobs = 1;
|
||||
my $opt_optimize;
|
||||
my $opt_quiet;
|
||||
my $opt_rerun;
|
||||
my $opt_rrsim;
|
||||
|
|
@ -104,7 +103,6 @@ if (! GetOptions(
|
|||
"hashset=s" => \$opt_hashset,
|
||||
"help" => \&usage,
|
||||
"j=i" => \$opt_jobs,
|
||||
"optimize:s" => \$opt_optimize,
|
||||
"quiet!" => \$opt_quiet,
|
||||
"rerun!" => \$opt_rerun,
|
||||
"rr!" => \$opt_rr,
|
||||
|
|
@ -661,7 +659,7 @@ sub new {
|
|||
verilator_define => 'VERILATOR',
|
||||
verilator_flags => ["-cc",
|
||||
"-Mdir $self->{obj_dir}",
|
||||
"-OD", # As currently disabled unless -O3
|
||||
"--fdedup", # As currently disabled unless -O3
|
||||
"--debug-check",
|
||||
"--comp-limit-members 10", ],
|
||||
verilator_flags2 => [],
|
||||
|
|
@ -924,7 +922,6 @@ sub compile_vlt_flags {
|
|||
unshift @verilator_flags, "--trace" if $opt_trace;
|
||||
my $threads = ::calc_threads($Vltmt_threads);
|
||||
unshift @verilator_flags, "--threads $threads" if $param{vltmt} && $checkflags !~ /-threads /;
|
||||
unshift @verilator_flags, "--trace-threads 1" if $param{vltmt} && $checkflags =~ /-trace /;
|
||||
unshift @verilator_flags, "--trace-threads 2" if $param{vltmt} && $checkflags =~ /-trace-fst /;
|
||||
unshift @verilator_flags, "--debug-partition" if $param{vltmt};
|
||||
unshift @verilator_flags, "-CFLAGS -ggdb -LDFLAGS -ggdb" if $opt_gdbsim;
|
||||
|
|
@ -935,19 +932,6 @@ sub compile_vlt_flags {
|
|||
$param{make_main} && $param{verilator_make_gmake};
|
||||
unshift @verilator_flags, "../" . $self->{main_filename} if
|
||||
$param{make_main} && $param{verilator_make_gmake};
|
||||
if (defined $opt_optimize) {
|
||||
my $letters = "";
|
||||
if ($opt_optimize =~ /[a-zA-Z]/) {
|
||||
$letters = $opt_optimize;
|
||||
} else { # Randomly turn on/off different optimizations
|
||||
foreach my $l ('a' .. 'z') {
|
||||
$letters .= ((rand() > 0.5) ? $l : uc $l);
|
||||
}
|
||||
unshift @verilator_flags, "--trace" if rand() > 0.5;
|
||||
unshift @verilator_flags, "--coverage" if rand() > 0.5;
|
||||
}
|
||||
unshift @verilator_flags, "--O" . $letters;
|
||||
}
|
||||
|
||||
my @cmdargs = (
|
||||
"--prefix " . $param{VM_PREFIX},
|
||||
|
|
@ -1892,6 +1876,7 @@ sub _make_main {
|
|||
$fh->print(" if (save_time && ${time} == save_time) {\n");
|
||||
$fh->print(" save_model(\"$self->{obj_dir}/saved.vltsv\");\n");
|
||||
$fh->print(" printf(\"Exiting after save_model\\n\");\n");
|
||||
$fh->print(" topp.reset(nullptr);\n");
|
||||
$fh->print(" return 0;\n");
|
||||
$fh->print(" }\n");
|
||||
}
|
||||
|
|
@ -2906,11 +2891,6 @@ Displays this message and program version and exits.
|
|||
Run number of parallel tests, or 0 to determine the count based on the
|
||||
number of cores installed. Requires Perl's Parallel::Forker package.
|
||||
|
||||
=item --optimize
|
||||
|
||||
Randomly turn on/off different optimizations. With specific flags,
|
||||
use those optimization settings
|
||||
|
||||
=item --quiet
|
||||
|
||||
Suppress all output except for failures and progress messages every 15
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public:
|
|||
__LINE__, m_info.product);
|
||||
}
|
||||
}
|
||||
~TestSimulator() {}
|
||||
~TestSimulator() = default;
|
||||
// METHORS
|
||||
private:
|
||||
static TestSimulator& singleton() {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ top_filename("t/t_altera_lpm.v");
|
|||
$module =~ s/_noinl//;
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--top-module ${module}", "-Oi"]
|
||||
verilator_flags2 => ["--top-module ${module}", "-fno-inline"]
|
||||
);
|
||||
|
||||
ok(1);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ scenarios(vlt_all => 1);
|
|||
|
||||
top_filename("t/t_alw_reorder.v");
|
||||
compile(
|
||||
verilator_flags2 => ["--stats -Or"],
|
||||
verilator_flags2 => ["--stats -fno-reorder"],
|
||||
);
|
||||
|
||||
file_grep($Self->{stats}, qr/Optimizations, Split always\s+(\d+)/i, 0);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["-O0 -OG"],
|
||||
verilator_flags2 => ["-O0 -fgate"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ scenarios(simulator => 1);
|
|||
top_filename("t_assign_slice_overflow.v");
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["-Ox"],
|
||||
verilator_flags2 => ["-fno-expand"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ scenarios(vlt => 1);
|
|||
top_filename("t/t_case_66bits.v");
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ['-Ox'],
|
||||
verilator_flags2 => ['-fno-expand'],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--trace --Os -x-assign 0"],
|
||||
verilator_flags2 => ["--trace --fno-split -x-assign 0"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--stats --O3 -x-assign fast"],
|
||||
verilator_flags2 => ["--stats -O3 -x-assign fast"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ scenarios(vlt => 1);
|
|||
top_filename("t/t_case_write1.v");
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ['-Ox'],
|
||||
verilator_flags2 => ['-fno-expand'],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--stats --O3 -x-assign fast"],
|
||||
verilator_flags2 => ["--stats -O3 -x-assign fast"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Wilson Snyder. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(linter => 1);
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ["--lint-only"],
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue