Merge from master for release.
This commit is contained in:
commit
82e1dbe162
|
|
@ -1,6 +1,7 @@
|
|||
# Build and push verilator docker image when tags are pushed to the repository.
|
||||
# The following secrets must be configured in the github repository:
|
||||
# The following variable(s) must be configured in the github repository:
|
||||
# DOCKER_HUB_NAMESPACE: docker hub namespace.
|
||||
# The following secrets must be configured in the github repository:
|
||||
# DOCKER_HUB_USER: user name for logging into docker hub
|
||||
# DOCKER_HUB_ACCESS_TOKEN: docker hub access token.
|
||||
name: Build Verilator Container
|
||||
|
|
@ -52,7 +53,7 @@ jobs:
|
|||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }}
|
||||
${{ vars.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }}
|
||||
tags: |
|
||||
type=match,pattern=(v.*),group=1,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=raw,value=${{ inputs.manual_tag }},enable=${{ inputs.manual_tag != '' }}
|
||||
|
|
|
|||
|
|
@ -41,3 +41,5 @@ verilator-config-version.cmake
|
|||
**/_build/*
|
||||
**/obj_dir/*
|
||||
/.vscode/
|
||||
/.idea/
|
||||
/cmake-build-*/
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
cmake_minimum_required(VERSION 3.15)
|
||||
cmake_policy(SET CMP0091 NEW) # Use MSVC_RUNTIME_LIBRARY to select the runtime
|
||||
project(Verilator
|
||||
VERSION 5.014
|
||||
VERSION 5.015
|
||||
HOMEPAGE_URL https://verilator.org
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
|
|
|||
51
Changes
51
Changes
|
|
@ -8,6 +8,57 @@ The changes in each Verilator version are described below. The
|
|||
contributors that suggested a given feature are shown in []. Thanks!
|
||||
|
||||
|
||||
Verilator 5.016 2023-09-16
|
||||
==========================
|
||||
|
||||
**Minor:**
|
||||
|
||||
* Add prepareClone and atClone APIs for Verilated models (#3503) (#4444). [Yinan Xu]
|
||||
* Add check for conflicting options e.g. binary and lint-only (#4409). [Ethan Sifferman]
|
||||
* Add --no-trace-top to not trace top signals (#4412) (#4422). [Frans Skarman]
|
||||
* Support recursive function calls (#3267).
|
||||
* Support assignments of packed values to stream expressions on queues (#4401). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Support no-parentheses calls to static methods (#4432). [Krzysztof Boroński]
|
||||
* Support block_item_declaration in forks (#4455). [Krzysztof Boroński]
|
||||
* Support assignments of stream expressions on queues to packed values (#4458). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Support function non-constant default arguments (#4470).
|
||||
* Support 'let'.
|
||||
* Optimize Verilator executable size by refactoring error reporting routines (#4446). [Anthony Donlon]
|
||||
* Optimize Verilation runtime pointers and graphs (#4396) (#4397) (#4398). [Krzysztof Bieganski, Antmicro Ltd]
|
||||
* Optimize preparations towards multithreaded Verilation (#4291) (#4463) (#4476) (#4477) (#4479). [Kamil Rakoczy, Antmicro Ltd]
|
||||
* Fix Windows filename format, etc (#3873) (#4421). [Anthony Donlon].
|
||||
* Fix t_dist_cppstyle Perl performance issue (#4085). [Srinivasan Venkataramanan]
|
||||
* Fix using type in parameterized classes without #() (#4281) (#4440). [Anthony Donlon]
|
||||
* Fix false INFINITELOOP on forever..mailbox.get() (#4323). [Srinivasan Venkataramanan]
|
||||
* Fix data type of condition operation on class objects (#4345) (#4352). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Fix variables mutated under fork..join_none/join_any blocks into anonymous objects (#4356). [Krzysztof Boroński]
|
||||
* Fix V3CUse, do not consider implementations (.cpp) at all (#4386). [Krzysztof Boroński]
|
||||
* Fix ++/-- under statements (#4399). [Aleksander Kiryk, Antmicro Ltd]
|
||||
* Fix detection of mixed blocking and nonblocking assignment in nested assignments (#4404). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Fix jumping over object initialization (#4411). [Krzysztof Boroński]
|
||||
* Fix multiple issues towards short circuit support (#4413) (#4460). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Fix variable lifetimes in extern methods (#4414). [Krzysztof Boroński]
|
||||
* Fix multiple function definitions in V3Sched (#4416). [Hennadii Chernyshchyk]
|
||||
* Fix false UNUSEDPARAM on generate localparam (#4427). [Bill Pringlemeir]
|
||||
* Fix checking for parameter and port connections in the wrong place (#4428). [Anthony Donlon]
|
||||
* Fix coroutine handle movement during queue manipulation (#4431). [Aleksander Kiryk, Antmicro Ltd]
|
||||
* Fix nested assignments on the LHS (#4435). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Fix false MULTITOP on bound interfaces (#4438). [Alex Solomatnikov]
|
||||
* Fix internal error on real conversion (#4447). [vdhotre-ventana]
|
||||
* Fix lifetime unknown error on enum.name (#4448). [jwoutersymatra]
|
||||
* Fix unstable output of VHashSha256 (#4453). [Anthony Donlon]
|
||||
* Fix static cast from a stream type (#4469) (#4485). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Fix error on enum with VARHIDDEN of cell (#4482). [Michail Rontionov]
|
||||
* Fix lint of case statements with enum and wildcard bits (#4464) (#4487). [Anthony Donlon]
|
||||
* Fix reference to extended class in parameterized class (#4466).
|
||||
* Fix recursive display causing segfault (#4480). [Kuoping Hsu]
|
||||
* Fix the error message when the type of ref argument is wrong (#4490). [Ryszard Rozak, Antmicro Ltd]
|
||||
* Fix display %x formatting of real.
|
||||
* Fix mis-warning on #() in classes' own functions.
|
||||
* Fix IGNOREDRETURN to not warn on void-cast static function calls.
|
||||
* Fix ZERODLY to not warn on 'wait(0)'.
|
||||
|
||||
|
||||
Verilator 5.014 2023-08-06
|
||||
==========================
|
||||
|
||||
|
|
|
|||
|
|
@ -440,6 +440,7 @@ detailed descriptions of these arguments.
|
|||
--trace-params Enable tracing of parameters
|
||||
--trace-structs Enable tracing structure names
|
||||
--trace-threads <threads> Enable FST waveform creation on separate threads
|
||||
--no-trace-top Do not emit traces for signals in the top module generated by verilator
|
||||
--trace-underscore Enable tracing of _signals
|
||||
-U<var> Undefine preprocessor define
|
||||
--no-unlimited-stack Don't disable stack size limit
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
|
|||
sudo apt-get install libsystemc libsystemc-dev
|
||||
fi
|
||||
if [ "$CI_RUNS_ON" = "ubuntu-22.04" ]; then
|
||||
sudo apt-get install mold ||
|
||||
sudo apt-get install mold
|
||||
sudo apt-get install bear mold ||
|
||||
sudo apt-get install bear mold
|
||||
fi
|
||||
if [ "$COVERAGE" = 1 ]; then
|
||||
yes yes | sudo cpan -fi Parallel::Forker
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
# Then 'make maintainer-dist'
|
||||
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
|
||||
#AC_INIT([Verilator],[#.### devel])
|
||||
AC_INIT([Verilator],[5.014 2023-08-06],
|
||||
AC_INIT([Verilator],[5.016 2023-09-16],
|
||||
[https://verilator.org],
|
||||
[verilator],[https://verilator.org])
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ Fan Shupei
|
|||
february cozzocrea
|
||||
Felix Neumärker
|
||||
Felix Yan
|
||||
Frans Skarman
|
||||
G-A. Kamendje
|
||||
Garrett Smith
|
||||
Geza Lore
|
||||
|
|
|
|||
|
|
@ -128,6 +128,43 @@ in the distribution. These headers use Doxygen comments, `///` and `//<`,
|
|||
to indicate and document those functions that are part of the Verilated
|
||||
public API.
|
||||
|
||||
Process-Level Clone APIs
|
||||
--------------------------
|
||||
|
||||
Modern operating systems support process-level clone (a.k.a copying, forking)
|
||||
with system call interfaces in C/C++, e.g., :code:`fork()` in Linux.
|
||||
|
||||
However, after cloning a parent process, some resources cannot be inherited
|
||||
in the child process. For example, in POSIX systems, when you fork a process,
|
||||
the child process inherits all the memory of the parent process. However,
|
||||
only the thread that called fork is replicated in the child process. Other
|
||||
threads are not.
|
||||
|
||||
Therefore, to support the process-level clone mechanisms, Verilator supports
|
||||
:code:`prepareClone()` and :code:`atClone()` APIs to allow the user to manually
|
||||
re-construct the model in the child process. The two APIs handle all necessary
|
||||
resources required for releasing and re-initializing before and after cloning.
|
||||
|
||||
The two APIs are supported in the verilated models. Here is an example of usage
|
||||
with Linux :code:`fork()` and :code:`pthread_atfork` APIs:
|
||||
|
||||
.. code-block:: C++
|
||||
|
||||
// static function pointers to fit pthread_atfork
|
||||
static auto prepareClone = [](){ topp->prepareClone(); };
|
||||
static auto atClone = [](){ topp->atClone(); };
|
||||
|
||||
// in main function, register the handlers:
|
||||
pthread_atfork(prepareClone, atClone, atClone);
|
||||
|
||||
For better flexibility, you can also manually call the handlers before and
|
||||
after :code:`fork()`.
|
||||
|
||||
With the process-level clone APIs, users can create process-level snapshots
|
||||
for the verilated models. While the Verilator save/restore option provides
|
||||
persistent and circuit-dependent snapshots, the process-level clone APIs
|
||||
enable in-memory, circuit-transparent, and highly efficient snapshots.
|
||||
|
||||
|
||||
Direct Programming Interface (DPI)
|
||||
==================================
|
||||
|
|
|
|||
|
|
@ -125,9 +125,12 @@ Summary:
|
|||
|
||||
After generating the SystemC/C++ code, Verilator will invoke the
|
||||
toolchain to build the model library (and executable when :vlopt:`--exe`
|
||||
is also used). Verilator manages the build itself, and for this --build
|
||||
is also used). Verilator manages the build itself, and for this --build
|
||||
requires GNU Make to be available on the platform.
|
||||
|
||||
:vlopt:`--build` cannot be specified when using :vlopt:`-E`,
|
||||
:vlopt:`--dpi-hdr-only`, :vlopt:`--lint-only`, or :vlopt:`--xml-only`.
|
||||
|
||||
.. option:: --build-dep-bin <filename>
|
||||
|
||||
Rarely needed. When a dependency (.d) file is created, this filename
|
||||
|
|
@ -1314,6 +1317,7 @@ Summary:
|
|||
This is not needed with standard designs with only one top. See also
|
||||
:option:`MULTITOP` warning.
|
||||
|
||||
|
||||
.. option:: --trace
|
||||
|
||||
Adds waveform tracing code to the model using VCD format. This overrides
|
||||
|
|
@ -1392,6 +1396,17 @@ Summary:
|
|||
This option is accepted, but has absolutely no effect with
|
||||
:vlopt:`--trace`, which respects :vlopt:`--threads` instead.
|
||||
|
||||
.. option:: --no-trace-top
|
||||
|
||||
Disables tracing for the input and output signals in the top wrapper which
|
||||
Verilator adds to the design. The signals are still traced in the original
|
||||
verilog top modules.
|
||||
|
||||
When combined with :option:`--main-top-name` set to "-" or when the name of
|
||||
the top module is set to "" in its constructor, the generated trace file
|
||||
will have the verilog top module as its root, rather than another module
|
||||
added by Verilator.
|
||||
|
||||
.. option:: --trace-underscore
|
||||
|
||||
Enable tracing of signals or modules that start with an
|
||||
|
|
@ -1523,13 +1538,15 @@ Summary:
|
|||
|
||||
.. option:: -Wno-lint
|
||||
|
||||
Disable all lint-related warning messages, and all style warnings. This is
|
||||
equivalent to ``-Wno-ALWCOMBORDER -Wno-ASCRANGE -Wno-BSSPACE -Wno-CASEINCOMPLETE
|
||||
-Wno-CASEOVERLAP -Wno-CASEX -Wno-CASTCONST -Wno-CASEWITHX -Wno-CMPCONST -Wno-COLONPLUS
|
||||
-Wno-IMPLICIT -Wno-IMPLICITSTATIC -Wno-PINCONNECTEMPTY
|
||||
-Wno-PINMISSING -Wno-STATICVAR -Wno-SYNCASYNCNET -Wno-UNDRIVEN -Wno-UNSIGNED
|
||||
-Wno-UNUSEDGENVAR -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL
|
||||
-Wno-WIDTH`` plus the list shown for Wno-style.
|
||||
Disable all lint-related warning messages, and all style warnings. This
|
||||
is equivalent to ``-Wno-ALWCOMBORDER`` ``-Wno-ASCRANGE``
|
||||
``-Wno-BSSPACE`` ``-Wno-CASEINCOMPLETE`` ``-Wno-CASEOVERLAP``
|
||||
``-Wno-CASEX`` ``-Wno-CASTCONST`` ``-Wno-CASEWITHX`` ``-Wno-CMPCONST``
|
||||
``-Wno-COLONPLUS`` ``-Wno-IMPLICIT`` ``-Wno-IMPLICITSTATIC``
|
||||
``-Wno-PINCONNECTEMPTY`` ``-Wno-PINMISSING`` ``-Wno-STATICVAR``
|
||||
``-Wno-SYNCASYNCNET`` ``-Wno-UNDRIVEN`` ``-Wno-UNSIGNED``
|
||||
``-Wno-UNUSEDGENVAR`` ``-Wno-UNUSEDPARAM`` ``-Wno-UNUSEDSIGNAL``
|
||||
``-Wno-WIDTH``, plus the list shown for :vlopt:`-Wno-style`.
|
||||
|
||||
It is strongly recommended that you clean up your code rather than using this
|
||||
option; it is only intended to be used when running test-cases of code
|
||||
|
|
@ -1537,12 +1554,13 @@ Summary:
|
|||
|
||||
.. option:: -Wno-style
|
||||
|
||||
Disable all code style related warning messages (note that by default, they are
|
||||
already disabled). This is equivalent to ``-Wno-DECLFILENAME -Wno-DEFPARAM
|
||||
-Wno-EOFNEWLINE -Wno-GENUNNAMED -Wno-IMPORTSTAR -Wno-INCABSPATH -Wno-PINCONNECTEMPTY
|
||||
-Wno-PINNOCONNECT -Wno-SYNCASYNCNET -Wno-UNDRIVEN
|
||||
-Wno-UNUSEDGENVAR -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL
|
||||
-Wno-VARHIDDEN``.
|
||||
Disable all code style related warning messages (note that by default,
|
||||
they are already disabled). This is equivalent to ``-Wno-DECLFILENAME``
|
||||
``-Wno-DEFPARAM`` ``-Wno-EOFNEWLINE`` ``-Wno-GENUNNAMED``
|
||||
``-Wno-IMPORTSTAR`` ``-Wno-INCABSPATH`` ``-Wno-PINCONNECTEMPTY``
|
||||
``-Wno-PINNOCONNECT`` ``-Wno-SYNCASYNCNET`` ``-Wno-UNDRIVEN``
|
||||
``-Wno-UNUSEDGENVAR`` ``-Wno-UNUSEDPARAM`` ``-Wno-UNUSEDSIGNAL``
|
||||
``-Wno-VARHIDDEN``.
|
||||
|
||||
.. option:: -Wpedantic
|
||||
|
||||
|
|
@ -1560,20 +1578,25 @@ Summary:
|
|||
|
||||
.. option:: -Wwarn-lint
|
||||
|
||||
Enable all lint-related warning messages (note that by default, they are already
|
||||
enabled), but do not affect style messages. This is equivalent to
|
||||
``-Wwarn-ALWCOMBORDER -Wwarn-ASCRANGE -Wwarn-BSSPACE -Wwarn-CASEINCOMPLETE
|
||||
-Wwarn-CASEOVERLAP -Wwarn-CASEWITHX -Wwarn-CASEX -Wwarn-CASTCONST -Wwarn-CMPCONST
|
||||
-Wwarn-COLONPLUS -Wwarn-IMPLICIT -Wwarn-IMPLICITSTATIC -Wwarn-LATCH -Wwarn-MISINDENT
|
||||
-Wwarn-NEWERSTD -Wwarn-PINMISSING -Wwarn-REALCVT -Wwarn-STATICVAR -Wwarn-UNSIGNED
|
||||
-Wwarn-WIDTHTRUNC -Wwarn-WIDTHEXPAND -Wwarn-WIDTHXZEXPAND``.
|
||||
Enable all lint-related warning messages (note that by default, they are
|
||||
already enabled), but do not affect style messages. This is equivalent
|
||||
to ``-Wwarn-ALWCOMBORDER`` ``-Wwarn-ASCRANGE`` ``-Wwarn-BSSPACE``
|
||||
``-Wwarn-CASEINCOMPLETE`` ``-Wwarn-CASEOVERLAP`` ``-Wwarn-CASEWITHX``
|
||||
``-Wwarn-CASEX`` ``-Wwarn-CASTCONST`` ``-Wwarn-CMPCONST``
|
||||
``-Wwarn-COLONPLUS`` ``-Wwarn-IMPLICIT`` ``-Wwarn-IMPLICITSTATIC``
|
||||
``-Wwarn-LATCH`` ``-Wwarn-MISINDENT`` ``-Wwarn-NEWERSTD``
|
||||
``-Wwarn-PINMISSING`` ``-Wwarn-REALCVT`` ``-Wwarn-STATICVAR``
|
||||
``-Wwarn-UNSIGNED`` ``-Wwarn-WIDTHTRUNC`` ``-Wwarn-WIDTHEXPAND``
|
||||
``-Wwarn-WIDTHXZEXPAND``.
|
||||
|
||||
.. option:: -Wwarn-style
|
||||
|
||||
Enable all code style-related warning messages. This is equivalent to
|
||||
``-Wwarn ASSIGNDLY -Wwarn-DECLFILENAME -Wwarn-DEFPARAM -Wwarn-EOFNEWLINE
|
||||
-Wwarn-GENUNNAMED -Wwarn-INCABSPATH -Wwarn-PINNOCONNECT -Wwarn-SYNCASYNCNET -Wwarn-UNDRIVEN
|
||||
-Wwarn-UNUSEDGENVAR -Wwarn-UNUSEDPARAM -Wwarn-UNUSEDSIGNAL -Wwarn-VARHIDDEN``.
|
||||
``-Wwarn-ASSIGNDLY`` ``-Wwarn-DECLFILENAME`` ``-Wwarn-DEFPARAM``
|
||||
``-Wwarn-EOFNEWLINE`` ``-Wwarn-GENUNNAMED`` ``-Wwarn-INCABSPATH``
|
||||
``-Wwarn-PINNOCONNECT`` ``-Wwarn-SYNCASYNCNET`` ``-Wwarn-UNDRIVEN``
|
||||
``-Wwarn-UNUSEDGENVAR`` ``-Wwarn-UNUSEDPARAM`` ``-Wwarn-UNUSEDSIGNAL``
|
||||
``-Wwarn-VARHIDDEN``.
|
||||
|
||||
.. option:: --x-assign 0
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ Those developing Verilator itself may also want these (see internals.rst):
|
|||
::
|
||||
|
||||
sudo apt-get install clang clang-format-14 cmake gdb gprof graphviz lcov
|
||||
sudo apt-get install libclang-dev yapf3
|
||||
sudo apt-get install libclang-dev yapf3 bear
|
||||
sudo pip3 install clang sphinx sphinx_rtd_theme sphinxcontrib-spelling breathe ruff
|
||||
cpan install Pod::Perldoc
|
||||
cpan install Parallel::Forker
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ The best place to get started is to try the :ref:`Examples`.
|
|||
.. [#] Verilog is defined by the `Institute of Electrical and Electronics
|
||||
Engineers (IEEE) Standard for Verilog Hardware Description
|
||||
Language`, Std. 1364, released in 1995, 2001, and 2005. The
|
||||
Verilator documentation uses the shorthand, e.g., "IEEE 1394-2005",
|
||||
Verilator documentation uses the shorthand, e.g., "IEEE 1364-2005",
|
||||
to refer to the, e.g., 2005 version of this standard.
|
||||
|
||||
.. [#] SystemVerilog is defined by the `Institute of Electrical and
|
||||
|
|
|
|||
|
|
@ -1990,11 +1990,15 @@ List Of Warnings
|
|||
|
||||
.. code-block:: sv
|
||||
|
||||
wait(0); // Blocks forever
|
||||
wait(1); // Blocks forever
|
||||
|
||||
Warns that a `wait` statement awaits a constant condition, which means it
|
||||
either blocks forever or never blocks.
|
||||
|
||||
As a special case `wait(0)` with the literal constant `0` (as opposed to
|
||||
something that elaborates to zero), does not warn, as it is presumed the
|
||||
code is making the intent clear.
|
||||
|
||||
|
||||
.. option:: WIDTH
|
||||
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ Maupin
|
|||
Mdir
|
||||
Mednick
|
||||
Menküc
|
||||
Michail
|
||||
Michiels
|
||||
Microsystems
|
||||
Milanovic
|
||||
|
|
@ -263,6 +264,7 @@ Pre
|
|||
Preprocess
|
||||
Pretet
|
||||
Pretl
|
||||
Pringlemeir
|
||||
Priyadharshini
|
||||
Pullup
|
||||
Pulver
|
||||
|
|
@ -277,6 +279,7 @@ Renga
|
|||
Requin
|
||||
Rodionov
|
||||
Rolfe
|
||||
Rontionov
|
||||
Roodselaar
|
||||
Runtime
|
||||
Ruud
|
||||
|
|
@ -415,6 +418,7 @@ arrarys
|
|||
assertOn
|
||||
astgen
|
||||
async
|
||||
atClone
|
||||
ato
|
||||
atoi
|
||||
autoconf
|
||||
|
|
@ -653,6 +657,7 @@ ish
|
|||
isunbounded
|
||||
isunknown
|
||||
jobserver
|
||||
jwoutersymatra
|
||||
killua
|
||||
lang
|
||||
lcov
|
||||
|
|
@ -772,6 +777,7 @@ pragmas
|
|||
pre
|
||||
precisions
|
||||
predefines
|
||||
prepareClone
|
||||
prepend
|
||||
prepended
|
||||
preprocess
|
||||
|
|
@ -930,7 +936,9 @@ vc
|
|||
vcd
|
||||
vcddiff
|
||||
vcoverage
|
||||
vdhotre
|
||||
vec
|
||||
ventana
|
||||
ver
|
||||
verFiles
|
||||
verible
|
||||
|
|
|
|||
|
|
@ -4181,7 +4181,7 @@ if(!(isfeof=feof(xc->fh)))
|
|||
if((xc->hier.u.attr.subtype == FST_MT_SOURCESTEM)||(xc->hier.u.attr.subtype == FST_MT_SOURCEISTEM))
|
||||
{
|
||||
int sidx_skiplen_dummy = 0;
|
||||
xc->hier.u.attr.arg_from_name = fstGetVarint64((unsigned char *)xc->str_scope_nam, &sidx_skiplen_dummy);
|
||||
xc->hier.u.attr.arg_from_name = fstGetVarint64((unsigned char *)xc->str_scope_attr, &sidx_skiplen_dummy);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ std::string VlRNG::get_randstate() const VL_MT_UNSAFE {
|
|||
const char* const stateCharsp = reinterpret_cast<const char*>(&m_state);
|
||||
static_assert(sizeof(m_state) == 16, "");
|
||||
std::string result{"R00112233445566770011223344556677"};
|
||||
for (int i = 0; i < sizeof(m_state); ++i) {
|
||||
for (size_t i = 0; i < sizeof(m_state); ++i) {
|
||||
result[1 + i * 2] = 'a' + ((stateCharsp[i] >> 4) & 15);
|
||||
result[1 + i * 2 + 1] = 'a' + (stateCharsp[i] & 15);
|
||||
}
|
||||
|
|
@ -332,7 +332,7 @@ void VlRNG::set_randstate(const std::string& state) VL_MT_UNSAFE {
|
|||
return;
|
||||
}
|
||||
char* const stateCharsp = reinterpret_cast<char*>(&m_state);
|
||||
for (int i = 0; i < sizeof(m_state); ++i) {
|
||||
for (size_t i = 0; i < sizeof(m_state); ++i) {
|
||||
stateCharsp[i]
|
||||
= (((state[1 + i * 2] - 'a') & 15) << 4) | ((state[1 + i * 2 + 1] - 'a') & 15);
|
||||
}
|
||||
|
|
@ -2608,6 +2608,14 @@ VerilatedVirtualBase* VerilatedContext::threadPoolp() {
|
|||
return m_threadPool.get();
|
||||
}
|
||||
|
||||
void VerilatedContext::prepareClone() { delete m_threadPool.release(); }
|
||||
|
||||
VerilatedVirtualBase* VerilatedContext::threadPoolpOnClone() {
|
||||
if (VL_UNLIKELY(m_threadPool)) m_threadPool.release();
|
||||
m_threadPool = std::unique_ptr<VlThreadPool>(new VlThreadPool{this, m_threads - 1});
|
||||
return m_threadPool.get();
|
||||
}
|
||||
|
||||
VerilatedVirtualBase*
|
||||
VerilatedContext::enableExecutionProfiler(VerilatedVirtualBase* (*construct)(VerilatedContext&)) {
|
||||
if (!m_executionProfiler) m_executionProfiler.reset(construct(*this));
|
||||
|
|
|
|||
|
|
@ -568,6 +568,8 @@ public:
|
|||
void addModel(VerilatedModel*);
|
||||
|
||||
VerilatedVirtualBase* threadPoolp();
|
||||
void prepareClone();
|
||||
VerilatedVirtualBase* threadPoolpOnClone();
|
||||
VerilatedVirtualBase*
|
||||
enableExecutionProfiler(VerilatedVirtualBase* (*construct)(VerilatedContext&));
|
||||
|
||||
|
|
|
|||
|
|
@ -1526,6 +1526,28 @@ static inline WDataOutP VL_STREAML_WWI(int lbits, WDataOutP owp, WDataInP const
|
|||
return owp;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void VL_ASSIGN_DYN_Q(VlQueue<T>& q, int elem_size, int lbits, QData from) {
|
||||
const int size = (lbits + elem_size - 1) / elem_size;
|
||||
q.renew(size);
|
||||
const QData mask = VL_MASK_Q(elem_size);
|
||||
for (int i = 0; i < size; ++i) q.at(i) = (T)((from >> (i * elem_size)) & mask);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline IData VL_DYN_TO_I(const VlQueue<T>& q, int elem_size) {
|
||||
IData ret = 0;
|
||||
for (int i = 0; i < q.size(); ++i) ret |= q.at(i) << (i * elem_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline QData VL_DYN_TO_Q(const VlQueue<T>& q, int elem_size) {
|
||||
QData ret = 0;
|
||||
for (int i = 0; i < q.size(); ++i) ret |= q.at(i) << (i * elem_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Because concats are common and wide, it's valuable to always have a clean output.
|
||||
// Thus we specify inputs must be clean, so we don't need to clean the output.
|
||||
// Note the bit shifts are always constants, so the adds in these constify out.
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ class VlCoroutineHandle final {
|
|||
public:
|
||||
// CONSTRUCTORS
|
||||
// Construct
|
||||
// non-explicit:
|
||||
// cppcheck-suppress noExplicitConstructor
|
||||
VlCoroutineHandle(VlProcessRef process)
|
||||
: m_coro{nullptr}
|
||||
, m_process{process} {
|
||||
|
|
@ -114,9 +116,11 @@ public:
|
|||
if (m_process) m_process->state(VlProcess::WAITING);
|
||||
}
|
||||
// Move the handle, leaving a nullptr
|
||||
// non-explicit:
|
||||
// cppcheck-suppress noExplicitConstructor
|
||||
VlCoroutineHandle(VlCoroutineHandle&& moved)
|
||||
: m_coro{std::exchange(moved.m_coro, nullptr)}
|
||||
, m_process{moved.m_process}
|
||||
, m_process{std::exchange(moved.m_process, nullptr)}
|
||||
, m_fileline{moved.m_fileline} {}
|
||||
// Destroy if the handle isn't null
|
||||
~VlCoroutineHandle() {
|
||||
|
|
@ -133,6 +137,8 @@ public:
|
|||
// Move the handle, leaving a null handle
|
||||
auto& operator=(VlCoroutineHandle&& moved) {
|
||||
m_coro = std::exchange(moved.m_coro, nullptr);
|
||||
m_process = std::exchange(moved.m_process, nullptr);
|
||||
m_fileline = moved.m_fileline;
|
||||
return *this;
|
||||
}
|
||||
// Resume the coroutine if the handle isn't null and the process isn't killed
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ public:
|
|||
VerilatedVpioVarBase(const VerilatedVar* varp, const VerilatedScope* scopep)
|
||||
: m_varp{varp}
|
||||
, m_scopep{scopep}
|
||||
, m_fullname{std::string{m_scopep->name()} + '.' + name()} {}
|
||||
, m_fullname{std::string{m_scopep->name()} + '.' + m_varp->name()} {}
|
||||
explicit VerilatedVpioVarBase(const VerilatedVpioVarBase* varp) {
|
||||
if (varp) {
|
||||
m_varp = varp->m_varp;
|
||||
|
|
|
|||
|
|
@ -94,20 +94,6 @@
|
|||
// Function requires a capability inbound (-fthread-safety)
|
||||
#define VL_CAPABILITY(x) \
|
||||
VL_CLANG_ATTR(capability(x))
|
||||
// Function requires not having a capability inbound (-fthread-safety)
|
||||
#define VL_REQUIRES(x) \
|
||||
VL_CLANG_ATTR(annotate("REQUIRES")) \
|
||||
VL_CLANG_ATTR(requires_capability(x))
|
||||
// Name of capability/lock (-fthread-safety)
|
||||
#define VL_GUARDED_BY(x) \
|
||||
VL_CLANG_ATTR(annotate("GUARDED_BY")) \
|
||||
VL_CLANG_ATTR(guarded_by(x))
|
||||
// The data that the annotated pointer points to is protected by the given capability.
|
||||
// The pointer itself is not protected.
|
||||
// Allowed on: pointer data member. (-fthread-safety)
|
||||
#define VL_PT_GUARDED_BY(x) \
|
||||
VL_CLANG_ATTR(annotate("PT_GUARDED_BY")) \
|
||||
VL_CLANG_ATTR(pt_guarded_by(x))
|
||||
// Name of mutex protecting this variable (-fthread-safety)
|
||||
#define VL_EXCLUDES(x) \
|
||||
VL_CLANG_ATTR(annotate("EXCLUDES")) \
|
||||
|
|
@ -124,6 +110,32 @@
|
|||
#define VL_ASSERT_CAPABILITY(x) \
|
||||
VL_CLANG_ATTR(assert_capability(x))
|
||||
|
||||
// Require mutex locks only in code units which work with enabled multi-threading.
|
||||
#if !defined(VL_MT_DISABLED_CODE_UNIT)
|
||||
// Function requires not having a capability inbound (-fthread-safety)
|
||||
# define VL_REQUIRES(x) \
|
||||
VL_CLANG_ATTR(annotate("REQUIRES")) \
|
||||
VL_CLANG_ATTR(requires_capability(x))
|
||||
// Name of capability/lock (-fthread-safety)
|
||||
# define VL_GUARDED_BY(x) \
|
||||
VL_CLANG_ATTR(annotate("GUARDED_BY")) \
|
||||
VL_CLANG_ATTR(guarded_by(x))
|
||||
// The data that the annotated pointer points to is protected by the given capability.
|
||||
// The pointer itself is not protected.
|
||||
// Allowed on: pointer data member. (-fthread-safety)
|
||||
# define VL_PT_GUARDED_BY(x) \
|
||||
VL_CLANG_ATTR(annotate("PT_GUARDED_BY")) \
|
||||
VL_CLANG_ATTR(pt_guarded_by(x))
|
||||
#else
|
||||
// Keep annotations for clang_check_attributes
|
||||
# define VL_REQUIRES(x) \
|
||||
VL_CLANG_ATTR(annotate("REQUIRES"))
|
||||
# define VL_GUARDED_BY(x) \
|
||||
VL_CLANG_ATTR(annotate("GUARDED_BY"))
|
||||
# define VL_PT_GUARDED_BY(x) \
|
||||
VL_CLANG_ATTR(annotate("PT_GUARDED_BY"))
|
||||
#endif
|
||||
|
||||
// Defaults for unsupported compiler features
|
||||
#ifndef VL_ATTR_ALWINLINE
|
||||
# define VL_ATTR_ALWINLINE ///< Attribute to inline, even when not optimizing
|
||||
|
|
@ -430,6 +442,11 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
|
|||
Type(const Type& other) = delete; \
|
||||
Type& operator=(const Type&) = delete
|
||||
|
||||
// Declare a class as unmovable; put after a private:
|
||||
#define VL_UNMOVABLE(Type) \
|
||||
Type(Type&& other) = delete; \
|
||||
Type& operator=(Type&&) = delete
|
||||
|
||||
//=========================================================================
|
||||
// Verilated function size macros
|
||||
|
||||
|
|
@ -577,8 +594,8 @@ static inline double VL_ROUND(double n) {
|
|||
//=========================================================================
|
||||
// Stringify macros
|
||||
|
||||
#define VL_STRINGIFY(x) VL_STRINGIFY2(x)
|
||||
#define VL_STRINGIFY2(x) #x
|
||||
#define VL_STRINGIFY(...) VL_STRINGIFY2(__VA_ARGS__)
|
||||
#define VL_STRINGIFY2(...) #__VA_ARGS__
|
||||
|
||||
//=========================================================================
|
||||
// Offset of field in type
|
||||
|
|
|
|||
|
|
@ -10,16 +10,36 @@ import argparse
|
|||
import os
|
||||
import sys
|
||||
import shlex
|
||||
from typing import Callable, Iterable, Optional, Union
|
||||
from typing import Callable, Iterable, Optional, Union, TYPE_CHECKING
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
from enum import Enum
|
||||
import multiprocessing
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
import clang.cindex
|
||||
from clang.cindex import CursorKind, Index, TranslationUnitSaveError, TranslationUnitLoadError
|
||||
from clang.cindex import (
|
||||
Index,
|
||||
TranslationUnitSaveError,
|
||||
TranslationUnitLoadError,
|
||||
CompilationDatabase,
|
||||
)
|
||||
|
||||
if not TYPE_CHECKING:
|
||||
from clang.cindex import CursorKind
|
||||
else:
|
||||
# Workaround for missing support for members defined out-of-class in Pylance:
|
||||
# https://github.com/microsoft/pylance-release/issues/2365#issuecomment-1035803067
|
||||
|
||||
class CursorKindMeta(type):
|
||||
|
||||
def __getattr__(cls, name: str) -> clang.cindex.CursorKind:
|
||||
return getattr(clang.cindex.CursorKind, name)
|
||||
|
||||
class CursorKind(clang.cindex.CursorKind, metaclass=CursorKindMeta):
|
||||
pass
|
||||
|
||||
|
||||
def fully_qualified_name(node):
|
||||
|
|
@ -60,6 +80,7 @@ class VlAnnotations:
|
|||
stable_tree: bool = False
|
||||
mt_safe_postinit: bool = False
|
||||
mt_unsafe: bool = False
|
||||
mt_disabled: bool = False
|
||||
mt_unsafe_one: bool = False
|
||||
pure: bool = False
|
||||
guarded: bool = False
|
||||
|
|
@ -81,7 +102,7 @@ class VlAnnotations:
|
|||
return self.stable_tree or self.mt_start
|
||||
|
||||
def is_mt_unsafe_call(self):
|
||||
return self.mt_unsafe or self.mt_unsafe_one
|
||||
return self.mt_unsafe or self.mt_unsafe_one or self.mt_disabled
|
||||
|
||||
def is_mt_safe_call(self):
|
||||
return (not self.is_mt_unsafe_call()
|
||||
|
|
@ -131,6 +152,8 @@ class VlAnnotations:
|
|||
result.mt_unsafe = True
|
||||
elif node.displayname == "MT_UNSAFE_ONE":
|
||||
result.mt_unsafe_one = True
|
||||
elif node.displayname == "MT_DISABLED":
|
||||
result.mt_disabled = True
|
||||
elif node.displayname == "PURE":
|
||||
result.pure = True
|
||||
elif node.displayname in ["ACQUIRE", "ACQUIRE_SHARED"]:
|
||||
|
|
@ -203,6 +226,19 @@ class FunctionInfo:
|
|||
def copy(self, /, **changes):
|
||||
return dataclasses.replace(self, **changes)
|
||||
|
||||
@staticmethod
|
||||
def from_decl_file_line_and_refd_node(file: str, line: int,
|
||||
refd: clang.cindex.Cursor,
|
||||
annotations: VlAnnotations):
|
||||
file = os.path.abspath(file)
|
||||
refd = refd.canonical
|
||||
assert refd is not None
|
||||
name_parts = fully_qualified_name(refd)
|
||||
usr = refd.get_usr()
|
||||
ftype = FunctionType.from_node(refd)
|
||||
|
||||
return FunctionInfo(name_parts, usr, file, line, annotations, ftype)
|
||||
|
||||
@staticmethod
|
||||
def from_node(node: clang.cindex.Cursor,
|
||||
refd: Optional[clang.cindex.Cursor] = None,
|
||||
|
|
@ -228,6 +264,7 @@ class DiagnosticKind(Enum):
|
|||
NON_PURE_CALL_IN_PURE_CTX = enum.auto()
|
||||
NON_MT_SAFE_CALL_IN_MT_SAFE_CTX = enum.auto()
|
||||
NON_STABLE_TREE_CALL_IN_STABLE_TREE_CTX = enum.auto()
|
||||
MISSING_MT_DISABLED_ANNOTATION = enum.auto()
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.value < other.value
|
||||
|
|
@ -265,35 +302,142 @@ class CallAnnotationsValidator:
|
|||
|
||||
self._index = Index.create()
|
||||
|
||||
self._processed_headers: set[str] = set()
|
||||
# Map key represents translation unit initial defines
|
||||
# (from command line and source's lines before any include)
|
||||
self._processed_headers: dict[str, set[str]] = {}
|
||||
self._external_decls: dict[str, set[tuple[str, int]]] = {}
|
||||
|
||||
# Current context
|
||||
self._main_source_file: str = ""
|
||||
self._defines: dict[str, str] = {}
|
||||
self._call_location: Optional[FunctionInfo] = None
|
||||
self._caller: Optional[FunctionInfo] = None
|
||||
self._level: int = 0
|
||||
self._constructor_context: list[clang.cindex.Cursor] = []
|
||||
self._level: int = 0
|
||||
|
||||
def is_mt_disabled_code_unit(self):
|
||||
return "VL_MT_DISABLED_CODE_UNIT" in self._defines
|
||||
|
||||
def is_constructor_context(self):
|
||||
return len(self._constructor_context) > 0
|
||||
|
||||
# Parses all lines in a form: `#define KEY VALUE` located before any `#include` line.
|
||||
# The parsing is very simple, there is no support for line breaks, etc.
|
||||
@staticmethod
|
||||
def parse_initial_defines(source_file: str) -> dict[str, str]:
|
||||
defs: dict[str, str] = {}
|
||||
with open(source_file, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
match = re.fullmatch(
|
||||
r"^#\s*(define\s+(\w+)(?:\s+(.*))?|include\s+.*)$", line)
|
||||
if match:
|
||||
if match.group(1).startswith("define"):
|
||||
key = match.group(2)
|
||||
value = match.groups("1")[2]
|
||||
defs[key] = value
|
||||
elif match.group(1).startswith("include"):
|
||||
break
|
||||
return defs
|
||||
|
||||
@staticmethod
|
||||
def filter_out_unsupported_compiler_args(
|
||||
args: list[str]) -> tuple[list[str], dict[str, str]]:
|
||||
filtered_args = []
|
||||
defines = {}
|
||||
args_iter = iter(args)
|
||||
try:
|
||||
while arg := next(args_iter):
|
||||
# Skip positional arguments (input file name).
|
||||
if not arg.startswith("-") and (arg.endswith(".cpp")
|
||||
or arg.endswith(".c")
|
||||
or arg.endswith(".h")):
|
||||
continue
|
||||
|
||||
# Skipped options with separate value argument.
|
||||
if arg in ["-o", "-T", "-MT", "-MQ", "-MF"
|
||||
"-L"]:
|
||||
next(args_iter)
|
||||
continue
|
||||
|
||||
# Skipped options without separate value argument.
|
||||
if arg == "-c" or arg.startswith("-W") or arg.startswith("-L"):
|
||||
continue
|
||||
|
||||
# Preserved options with separate value argument.
|
||||
if arg in [
|
||||
"-x"
|
||||
"-Xclang", "-I", "-isystem", "-iquote", "-include",
|
||||
"-include-pch"
|
||||
]:
|
||||
filtered_args += [arg, next(args_iter)]
|
||||
continue
|
||||
|
||||
kv_str = None
|
||||
d_or_u = None
|
||||
# Preserve define/undefine with separate value argument.
|
||||
if arg in ["-D", "-U"]:
|
||||
filtered_args.append(arg)
|
||||
d_or_u = arg[1]
|
||||
kv_str = next(args_iter)
|
||||
filtered_args.append(kv_str)
|
||||
# Preserve define/undefine without separate value argument.
|
||||
elif arg[0:2] in ["-D", "-U"]:
|
||||
filtered_args.append(arg)
|
||||
kv_str = arg[2:]
|
||||
d_or_u = arg[1]
|
||||
# Preserve everything else.
|
||||
else:
|
||||
filtered_args.append(arg)
|
||||
continue
|
||||
|
||||
# Keep track of defines for class' internal purposes.
|
||||
key_value = kv_str.split("=", 1)
|
||||
key = key_value[0]
|
||||
val = "1" if len(key_value) == 1 else key_value[1]
|
||||
|
||||
if d_or_u == "D":
|
||||
defines[key] = val
|
||||
elif d_or_u == "U" and key in defines:
|
||||
del defines[key]
|
||||
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
return (filtered_args, defines)
|
||||
|
||||
def compile_and_analyze_file(self, source_file: str,
|
||||
compiler_args: list[str],
|
||||
build_dir: Optional[str]):
|
||||
filename = os.path.abspath(source_file)
|
||||
initial_cwd = "."
|
||||
|
||||
filtered_args, defines = self.filter_out_unsupported_compiler_args(
|
||||
compiler_args)
|
||||
defines.update(self.parse_initial_defines(source_file))
|
||||
|
||||
if build_dir:
|
||||
initial_cwd = os.getcwd()
|
||||
os.chdir(build_dir)
|
||||
translation_unit = self._index.parse(filename, compiler_args)
|
||||
has_errors = False
|
||||
for diag in translation_unit.diagnostics:
|
||||
if diag.severity > clang.cindex.Diagnostic.Error:
|
||||
has_errors = True
|
||||
if translation_unit and not has_errors:
|
||||
try:
|
||||
translation_unit = self._index.parse(filename, filtered_args)
|
||||
except TranslationUnitLoadError:
|
||||
translation_unit = None
|
||||
errors = []
|
||||
if translation_unit:
|
||||
for diag in translation_unit.diagnostics:
|
||||
if diag.severity >= clang.cindex.Diagnostic.Error:
|
||||
errors.append(str(diag))
|
||||
if translation_unit and len(errors) == 0:
|
||||
self._defines = defines
|
||||
self._main_source_file = filename
|
||||
self.process_translation_unit(translation_unit)
|
||||
self._main_source_file = ""
|
||||
self._defines = {}
|
||||
else:
|
||||
print(f"%Error: parsing failed: {filename}", file=sys.stderr)
|
||||
for error in errors:
|
||||
print(f" {error}", file=sys.stderr)
|
||||
if build_dir:
|
||||
os.chdir(initial_cwd)
|
||||
|
||||
|
|
@ -563,6 +707,18 @@ class CallAnnotationsValidator:
|
|||
f" from: {node.location.file.name}:{node.location.line}")
|
||||
return True
|
||||
|
||||
def process_function_declaration(self, node: clang.cindex.Cursor):
|
||||
# Ignore declarations in main .cpp file
|
||||
if node.location.file.name != self._main_source_file:
|
||||
children = list(node.get_children())
|
||||
annotations = VlAnnotations.from_nodes_list(children)
|
||||
if not annotations.mt_disabled:
|
||||
self._external_decls.setdefault(node.get_usr(), set()).add(
|
||||
(str(node.location.file.name), int(node.location.line)))
|
||||
return self.iterate_children(children, self.dispatch_node)
|
||||
|
||||
return self.iterate_children(node.get_children(), self.dispatch_node)
|
||||
|
||||
# Definition handling
|
||||
|
||||
def dispatch_node_inside_definition(self, node: clang.cindex.Cursor):
|
||||
|
|
@ -593,6 +749,18 @@ class CallAnnotationsValidator:
|
|||
assert refd is not None
|
||||
|
||||
def_annotations = VlAnnotations.from_nodes_list(node_children)
|
||||
# Implicitly mark definitions in VL_MT_DISABLED_CODE_UNIT .cpp files as
|
||||
# VL_MT_DISABLED. Existence of the annotation on declarations in .h
|
||||
# files is verified below.
|
||||
# Also sets VL_REQUIRES, as this annotation is added together with
|
||||
# explicit VL_MT_DISABLED.
|
||||
if self.is_mt_disabled_code_unit():
|
||||
if node.location.file.name == self._main_source_file:
|
||||
annotations.mt_disabled = True
|
||||
annotations.requires = True
|
||||
if refd.location.file.name == self._main_source_file:
|
||||
def_annotations.mt_disabled = True
|
||||
def_annotations.requires = True
|
||||
|
||||
if not (def_annotations.is_empty() or def_annotations == annotations):
|
||||
# Use definition's annotations for the diagnostic
|
||||
|
|
@ -605,12 +773,26 @@ class CallAnnotationsValidator:
|
|||
DiagnosticKind.ANNOTATIONS_DEF_DECL_MISMATCH)
|
||||
|
||||
# Use concatenation of definition and declaration annotations
|
||||
# for callees validation.
|
||||
# for calls validation.
|
||||
self._caller = FunctionInfo.from_node(node, refd,
|
||||
def_annotations | annotations)
|
||||
prev_call_location = self._call_location
|
||||
self._call_location = self._caller
|
||||
|
||||
if self.is_mt_disabled_code_unit():
|
||||
# Report declarations of this functions that don't have MT_DISABLED annotation
|
||||
# and are located in headers.
|
||||
if node.location.file.name == self._main_source_file:
|
||||
usr = node.get_usr()
|
||||
declarations = self._external_decls.get(usr, set())
|
||||
for file, line in declarations:
|
||||
self.emit_diagnostic(
|
||||
FunctionInfo.from_decl_file_line_and_refd_node(
|
||||
file, line, refd, def_annotations),
|
||||
DiagnosticKind.MISSING_MT_DISABLED_ANNOTATION)
|
||||
if declarations:
|
||||
del self._external_decls[usr]
|
||||
|
||||
self.iterate_children(node_children,
|
||||
self.dispatch_node_inside_definition)
|
||||
|
||||
|
|
@ -622,34 +804,36 @@ class CallAnnotationsValidator:
|
|||
# Nodes not located inside definition
|
||||
|
||||
def dispatch_node(self, node: clang.cindex.Cursor):
|
||||
if node.is_definition() and node.kind in [
|
||||
if node.kind in [
|
||||
CursorKind.CXX_METHOD, CursorKind.FUNCTION_DECL,
|
||||
CursorKind.CONSTRUCTOR, CursorKind.CONVERSION_FUNCTION
|
||||
]:
|
||||
return self.process_function_definition(node)
|
||||
if node.is_definition() and node.kind in [
|
||||
CursorKind.NAMESPACE, CursorKind.STRUCT_DECL,
|
||||
CursorKind.UNION_DECL, CursorKind.CLASS_DECL
|
||||
]:
|
||||
return self.iterate_children(node.get_children(),
|
||||
self.dispatch_node)
|
||||
if node.is_definition():
|
||||
return self.process_function_definition(node)
|
||||
# else:
|
||||
return self.process_function_declaration(node)
|
||||
|
||||
return self.iterate_children(node.get_children(), self.dispatch_node)
|
||||
|
||||
def process_translation_unit(
|
||||
self, translation_unit: clang.cindex.TranslationUnit):
|
||||
self._level += 1
|
||||
kv_defines = sorted([f"{k}={v}" for k, v in self._defines.items()])
|
||||
concat_defines = '\n'.join(kv_defines)
|
||||
# List of headers already processed in a TU with specified set of defines.
|
||||
tu_processed_headers = self._processed_headers.setdefault(
|
||||
concat_defines, set())
|
||||
for child in translation_unit.cursor.get_children():
|
||||
if self._is_ignored_top_level(child):
|
||||
continue
|
||||
if self._processed_headers:
|
||||
if tu_processed_headers:
|
||||
filename = os.path.abspath(child.location.file.name)
|
||||
if filename in self._processed_headers:
|
||||
if filename in tu_processed_headers:
|
||||
continue
|
||||
self.dispatch_node(child)
|
||||
self._level -= 1
|
||||
|
||||
self._processed_headers.update([
|
||||
tu_processed_headers.update([
|
||||
os.path.abspath(str(hdr.source))
|
||||
for hdr in translation_unit.get_includes()
|
||||
])
|
||||
|
|
@ -711,34 +895,40 @@ def get_filter_funcs(verilator_root: str):
|
|||
|
||||
|
||||
def precompile_header(compile_command: CompileCommand, tmp_dir: str) -> str:
|
||||
initial_cwd = os.getcwd()
|
||||
errors = []
|
||||
try:
|
||||
initial_cwd = os.getcwd()
|
||||
os.chdir(compile_command.directory)
|
||||
|
||||
index = Index.create()
|
||||
translation_unit = index.parse(compile_command.filename,
|
||||
compile_command.args)
|
||||
for diag in translation_unit.diagnostics:
|
||||
if diag.severity > clang.cindex.Diagnostic.Error:
|
||||
pch_file = None
|
||||
break
|
||||
else:
|
||||
if diag.severity >= clang.cindex.Diagnostic.Error:
|
||||
errors.append(str(diag))
|
||||
|
||||
if len(errors) == 0:
|
||||
pch_file = os.path.join(
|
||||
tmp_dir,
|
||||
f"{compile_command.refid:02}_{os.path.basename(compile_command.filename)}.pch"
|
||||
)
|
||||
translation_unit.save(pch_file)
|
||||
|
||||
if pch_file:
|
||||
return pch_file
|
||||
|
||||
except (TranslationUnitSaveError, TranslationUnitLoadError,
|
||||
OSError) as exception:
|
||||
print(f"%Warning: {exception}", file=sys.stderr)
|
||||
|
||||
finally:
|
||||
os.chdir(initial_cwd)
|
||||
|
||||
if pch_file:
|
||||
return pch_file
|
||||
|
||||
except (TranslationUnitSaveError, TranslationUnitLoadError, OSError):
|
||||
pass
|
||||
|
||||
print(
|
||||
f"%Warning: Precompiling failed, skipping: {compile_command.filename}")
|
||||
f"%Warning: Precompilation failed, skipping: {compile_command.filename}",
|
||||
file=sys.stderr)
|
||||
for error in errors:
|
||||
print(f" {error}", file=sys.stderr)
|
||||
return ""
|
||||
|
||||
|
||||
|
|
@ -761,7 +951,7 @@ def run_analysis(ccl: Iterable[CompileCommand], pccl: Iterable[CompileCommand],
|
|||
is_ignored_def, is_ignored_call)
|
||||
for compile_command in ccl:
|
||||
cav.compile_and_analyze_file(compile_command.filename,
|
||||
compile_command.args + extra_args,
|
||||
extra_args + compile_command.args,
|
||||
compile_command.directory)
|
||||
|
||||
|
||||
|
|
@ -881,6 +1071,10 @@ class TopDownSummaryPrinter():
|
|||
name += "is pure but calls non-pure function(s)"
|
||||
elif func.reason == DiagnosticKind.NON_STABLE_TREE_CALL_IN_STABLE_TREE_CTX:
|
||||
name += "is stable_tree but calls non-stable_tree or non-mtsafe"
|
||||
elif func.reason == DiagnosticKind.MISSING_MT_DISABLED_ANNOTATION:
|
||||
name += ("defined in a file marked as " +
|
||||
"VL_MT_DISABLED_CODE_UNIT has declaration(s) " +
|
||||
"without VL_MT_DISABLED annotation")
|
||||
else:
|
||||
name += "for unknown reason (please add description)"
|
||||
|
||||
|
|
@ -947,10 +1141,15 @@ def main():
|
|||
type=int,
|
||||
default=0,
|
||||
help="Number of parallel jobs to use.")
|
||||
parser.add_argument(
|
||||
"--compile-commands-dir",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Path to directory containing compile_commands.json.")
|
||||
parser.add_argument("--cxxflags",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Flags passed to clang++.")
|
||||
help="Extra flags passed to clang++.")
|
||||
parser.add_argument(
|
||||
"--compilation-root",
|
||||
type=str,
|
||||
|
|
@ -975,34 +1174,52 @@ def main():
|
|||
cmdline.compilation_root = cmdline.verilator_root
|
||||
|
||||
verilator_root = os.path.abspath(cmdline.verilator_root)
|
||||
compilation_root = os.path.abspath(cmdline.compilation_root)
|
||||
default_compilation_root = os.path.abspath(cmdline.compilation_root)
|
||||
|
||||
compdb: Optional[CompilationDatabase] = None
|
||||
if cmdline.compile_commands_dir:
|
||||
compdb = CompilationDatabase.fromDirectory(
|
||||
cmdline.compile_commands_dir)
|
||||
|
||||
default_cxx_flags = [
|
||||
f"-I{verilator_root}/src",
|
||||
f"-I{verilator_root}/include",
|
||||
f"-I{verilator_root}/src/obj_opt",
|
||||
"-fcoroutines-ts",
|
||||
]
|
||||
if cmdline.cxxflags is not None:
|
||||
cxxflags = shlex.split(cmdline.cxxflags)
|
||||
common_cxxflags = shlex.split(cmdline.cxxflags)
|
||||
else:
|
||||
cxxflags = default_cxx_flags
|
||||
common_cxxflags = []
|
||||
|
||||
precompile_commands_list = []
|
||||
|
||||
if cmdline.precompile:
|
||||
hdr_cxxflags = ['-xc++-header'] + cxxflags
|
||||
hdr_cxxflags = ['-xc++-header'] + common_cxxflags
|
||||
for refid, file in enumerate(cmdline.precompile):
|
||||
filename = os.path.abspath(file)
|
||||
compile_command = CompileCommand(refid, filename, hdr_cxxflags,
|
||||
compilation_root)
|
||||
default_compilation_root)
|
||||
precompile_commands_list.append(compile_command)
|
||||
|
||||
compile_commands_list = []
|
||||
for refid, file in enumerate(cmdline.file):
|
||||
filename = os.path.abspath(file)
|
||||
compile_command = CompileCommand(refid, filename, cxxflags,
|
||||
compilation_root)
|
||||
root = default_compilation_root
|
||||
cxxflags = []
|
||||
if compdb:
|
||||
entry = compdb.getCompileCommands(filename)
|
||||
entry_list = list(entry)
|
||||
# Compilation database can contain multiple entries for single file,
|
||||
# e.g. when it has been updated by appending new entries.
|
||||
# Use last entry for the file, if it exists, as it is the newest one.
|
||||
if len(entry_list) > 0:
|
||||
last_entry = entry_list[-1]
|
||||
root = last_entry.directory
|
||||
entry_args = list(last_entry.arguments)
|
||||
# First argument in compile_commands.json arguments list is
|
||||
# compiler executable name/path. CIndex (libclang) always
|
||||
# implicitly prepends executable name, so it shouldn't be passed
|
||||
# here.
|
||||
cxxflags = common_cxxflags + entry_args[1:]
|
||||
else:
|
||||
cxxflags = common_cxxflags[:]
|
||||
|
||||
compile_command = CompileCommand(refid, filename, cxxflags, root)
|
||||
compile_commands_list.append(compile_command)
|
||||
|
||||
summary_printer = TopDownSummaryPrinter()
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ set(HEADERS
|
|||
V3Table.h
|
||||
V3Task.h
|
||||
V3ThreadPool.h
|
||||
V3ThreadSafety.h
|
||||
V3Timing.h
|
||||
V3Trace.h
|
||||
V3TraceDecl.h
|
||||
|
|
@ -490,11 +491,13 @@ target_include_directories(${verilator}
|
|||
)
|
||||
|
||||
if (WIN32)
|
||||
target_compile_options(${verilator} PRIVATE /bigobj)
|
||||
target_compile_definitions(${verilator} PRIVATE
|
||||
YY_NO_UNISTD_H
|
||||
)
|
||||
target_include_directories(${verilator} PRIVATE ../platform/win32)
|
||||
target_link_libraries(${verilator} PRIVATE bcrypt psapi)
|
||||
target_link_options(${verilator} PRIVATE /STACK:10000000)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${verilator})
|
||||
|
|
|
|||
|
|
@ -18,14 +18,13 @@
|
|||
#### Start of system configuration section. ####
|
||||
|
||||
srcdir = @srcdir@
|
||||
PYTHON3 = @PYTHON3@
|
||||
EXEEXT = @EXEEXT@
|
||||
PYTHON3 = @PYTHON3@
|
||||
# VPATH only does sources; fix install_test's not creating ../bin
|
||||
vpath %.h @srcdir@
|
||||
|
||||
#### End of system configuration section. ####
|
||||
|
||||
|
||||
default: dbg opt
|
||||
debug: dbg
|
||||
optimize: opt
|
||||
|
|
@ -36,6 +35,26 @@ endif
|
|||
|
||||
UNDER_GIT = $(wildcard ${srcdir}/../.git/logs/HEAD)
|
||||
|
||||
ifeq (,$(wildcard obj_dbg/bear.o))
|
||||
ifneq (, $(shell which bear 2>/dev/null))
|
||||
BEAR := $(shell which bear)
|
||||
ifeq (, $(shell $(BEAR) --output obj_dbg/comptest.json -- true))
|
||||
$(shell which bear 2>/dev/null >obj_dbg/bear.o)
|
||||
else
|
||||
# unsupported version
|
||||
BEAR :=
|
||||
endif
|
||||
endif
|
||||
else
|
||||
BEAR := $(shell cat obj_dbg/bear.o)
|
||||
endif
|
||||
|
||||
ifneq ($(BEAR),)
|
||||
BEAR_OBJ_OPT := $(BEAR) --append --output obj_dbg/compile_commands.json --
|
||||
else
|
||||
BEAR_OBJ_OPT :=
|
||||
endif
|
||||
|
||||
#*********************************************************************
|
||||
|
||||
obj_opt:
|
||||
|
|
@ -62,8 +81,8 @@ endif
|
|||
|
||||
dbg: ../bin/verilator_bin_dbg$(EXEEXT) ../bin/verilator_coverage_bin_dbg$(EXEEXT)
|
||||
../bin/verilator_bin_dbg$(EXEEXT): obj_dbg ../bin prefiles
|
||||
$(MAKE) -C obj_dbg -j 1 TGT=../$@ VL_DEBUG=1 -f ../Makefile_obj serial
|
||||
$(MAKE) -C obj_dbg TGT=../$@ VL_DEBUG=1 -f ../Makefile_obj
|
||||
$(BEAR_OBJ_OPT) $(MAKE) -C obj_dbg -j 1 TGT=../$@ VL_DEBUG=1 -f ../Makefile_obj serial
|
||||
$(BEAR_OBJ_OPT) $(MAKE) -C obj_dbg TGT=../$@ VL_DEBUG=1 -f ../Makefile_obj
|
||||
|
||||
../bin/verilator_coverage_bin_dbg$(EXEEXT): obj_dbg ../bin prefiles
|
||||
$(MAKE) -C obj_dbg TGT=../$@ VL_DEBUG=1 VL_VLCOV=1 -f ../Makefile_obj serial_vlcov
|
||||
|
|
|
|||
|
|
@ -289,6 +289,7 @@ NON_STANDALONE_HEADERS = \
|
|||
V3AstNodeExpr.h \
|
||||
V3AstNodeOther.h \
|
||||
V3DfgVertices.h \
|
||||
V3ThreadPool.h \
|
||||
V3WidthCommit.h \
|
||||
|
||||
AST_DEFS := \
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
|||
// Extend V3GraphVertex class for use in latch detection graph
|
||||
|
||||
class LatchDetectGraphVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(LatchDetectGraphVertex, V3GraphVertex)
|
||||
public:
|
||||
enum VertexType : uint8_t { VT_BLOCK, VT_BRANCH, VT_OUTPUT };
|
||||
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ private:
|
|||
ifp = nextifp;
|
||||
} while (ifp);
|
||||
|
||||
AstNode* const newifp = nodep->cloneTree(false);
|
||||
AstIf* const newifp = nodep->cloneTree(false);
|
||||
const bool allow_none = nodep->unique0Pragma();
|
||||
|
||||
// Empty case means no property
|
||||
|
|
|
|||
|
|
@ -55,12 +55,28 @@ bool VNUser5InUse::s_userBusy = false;
|
|||
int AstNodeDType::s_uniqueNum = 0;
|
||||
|
||||
//######################################################################
|
||||
// V3AstType
|
||||
// VNType
|
||||
|
||||
const VNTypeInfo VNType::typeInfoTable[] = {
|
||||
#include "V3Ast__gen_type_info.h" // From ./astgen
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, VNType rhs);
|
||||
|
||||
//######################################################################
|
||||
// Creators
|
||||
// VSelfPointerText
|
||||
|
||||
const std::shared_ptr<const string> VSelfPointerText::s_emptyp = std::make_shared<string>("");
|
||||
const std::shared_ptr<const string> VSelfPointerText::s_thisp = std::make_shared<string>("this");
|
||||
|
||||
string VSelfPointerText::protect(bool useSelfForThis, bool protect) const {
|
||||
const string& sp
|
||||
= useSelfForThis ? VString::replaceWord(asString(), "this", "vlSelf") : asString();
|
||||
return VIdProtect::protectWordsIf(sp, protect);
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// AstNode
|
||||
|
||||
AstNode::AstNode(VNType t, FileLine* fl)
|
||||
: m_type{t}
|
||||
|
|
@ -1098,9 +1114,57 @@ bool AstNode::sameTreeIter(const AstNode* node1p, const AstNode* node2p, bool ig
|
|||
void AstNode::checkTreeIter(const AstNode* prevBackp) const VL_MT_STABLE {
|
||||
// private: Check a tree and children
|
||||
UASSERT_OBJ(prevBackp == this->backp(), this, "Back node inconsistent");
|
||||
switch (this->type()) {
|
||||
#include "V3Ast__gen_op_checks.h"
|
||||
default: VL_UNREACHABLE; // LCOV_EXCL_LINE
|
||||
const VNTypeInfo& typeInfo = *type().typeInfo();
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
AstNode* nodep = nullptr;
|
||||
switch (i) {
|
||||
case 1: nodep = op1p(); break;
|
||||
case 2: nodep = op2p(); break;
|
||||
case 3: nodep = op3p(); break;
|
||||
case 4: nodep = op4p(); break;
|
||||
default: this->v3fatalSrc("Bad case"); break;
|
||||
}
|
||||
const char* opName = typeInfo.m_opNamep[i - 1];
|
||||
switch (typeInfo.m_opType[i - 1]) {
|
||||
case VNTypeInfo::OP_UNUSED:
|
||||
UASSERT_OBJ(!nodep, this, typeInfo.m_namep << " must not use " << opName << "()");
|
||||
break;
|
||||
case VNTypeInfo::OP_USED:
|
||||
UASSERT_OBJ(nodep, this,
|
||||
typeInfo.m_namep << " must have non nullptr " << opName << "()");
|
||||
UASSERT_OBJ(!nodep->nextp(), this,
|
||||
typeInfo.m_namep << "::" << opName
|
||||
<< "() cannot have a non nullptr nextp()");
|
||||
nodep->checkTreeIter(this);
|
||||
break;
|
||||
case VNTypeInfo::OP_LIST:
|
||||
if (const AstNode* const headp = nodep) {
|
||||
const AstNode* backp = this;
|
||||
const AstNode* tailp = headp;
|
||||
const AstNode* opp = headp;
|
||||
do {
|
||||
opp->checkTreeIter(backp);
|
||||
UASSERT_OBJ(opp == headp || !opp->nextp() || !opp->m_headtailp, opp,
|
||||
"Headtailp should be null in middle of lists");
|
||||
backp = tailp = opp;
|
||||
opp = opp->nextp();
|
||||
} while (opp);
|
||||
UASSERT_OBJ(headp->m_headtailp == tailp, headp,
|
||||
"Tail in headtailp is inconsistent");
|
||||
UASSERT_OBJ(tailp->m_headtailp == headp, tailp,
|
||||
"Head in headtailp is inconsistent");
|
||||
}
|
||||
break;
|
||||
case VNTypeInfo::OP_OPTIONAL:
|
||||
if (nodep) {
|
||||
UASSERT_OBJ(!nodep->nextp(), this,
|
||||
typeInfo.m_namep << "::" << opName
|
||||
<< "() cannot have a non-nullptr nextp()");
|
||||
nodep->checkTreeIter(this);
|
||||
}
|
||||
break;
|
||||
default: this->v3fatalSrc("Bad case"); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1289,12 +1353,6 @@ bool AstNode::isTreePureRecurse() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
void AstNode::v3errorEndFatal(std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
v3errorEnd(str);
|
||||
assert(0); // LCOV_EXCL_LINE
|
||||
VL_UNREACHABLE;
|
||||
}
|
||||
|
||||
string AstNode::instanceStr() const {
|
||||
// Max iterations before giving up on location search,
|
||||
// in case we have some circular reference bug.
|
||||
|
|
@ -1319,9 +1377,9 @@ string AstNode::instanceStr() const {
|
|||
|
||||
return "";
|
||||
}
|
||||
void AstNode::v3errorEnd(std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
void AstNode::v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
|
||||
if (!m_fileline) {
|
||||
V3Error::s().v3errorEnd(str, instanceStr());
|
||||
V3Error::v3errorEnd(str, instanceStr());
|
||||
} else {
|
||||
std::ostringstream nsstr;
|
||||
nsstr << str.str();
|
||||
|
|
@ -1338,6 +1396,11 @@ void AstNode::v3errorEnd(std::ostringstream& str) const VL_REQUIRES(V3Error::s()
|
|||
nsstr, m_fileline->warnIsOff(V3Error::s().errorCode()) ? "" : instanceStr());
|
||||
}
|
||||
}
|
||||
void AstNode::v3errorEndFatal(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
|
||||
v3errorEnd(str);
|
||||
assert(0); // LCOV_EXCL_LINE
|
||||
VL_UNREACHABLE;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
// Data type conversion
|
||||
|
|
|
|||
225
src/V3Ast.h
225
src/V3Ast.h
|
|
@ -98,7 +98,20 @@ using MTaskIdSet = std::set<int>; // Set of mtaskIds for Var sorting
|
|||
|
||||
//######################################################################
|
||||
|
||||
struct VNTypeInfo {
|
||||
const char* m_namep;
|
||||
enum uint8_t {
|
||||
OP_UNUSED,
|
||||
OP_USED,
|
||||
OP_LIST,
|
||||
OP_OPTIONAL,
|
||||
} m_opType[4];
|
||||
const char* m_opNamep[4];
|
||||
};
|
||||
|
||||
class VNType final {
|
||||
static const VNTypeInfo typeInfoTable[];
|
||||
|
||||
public:
|
||||
#include "V3Ast__gen_type_enum.h" // From ./astgen
|
||||
// Above include has:
|
||||
|
|
@ -111,6 +124,7 @@ public:
|
|||
constexpr VNType(en _e) VL_MT_SAFE : m_e{_e} {}
|
||||
explicit VNType(int _e)
|
||||
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
|
||||
const VNTypeInfo* typeInfo() const VL_MT_SAFE { return &typeInfoTable[m_e]; }
|
||||
constexpr operator en() const VL_MT_SAFE { return m_e; }
|
||||
};
|
||||
constexpr bool operator==(const VNType& lhs, const VNType& rhs) VL_PURE {
|
||||
|
|
@ -802,6 +816,7 @@ public:
|
|||
"BLOCKTEMP", "MODULETEMP", "STMTTEMP", "XTEMP", "IFACEREF", "MEMBER"};
|
||||
return names[m_e];
|
||||
}
|
||||
bool isParam() const { return m_e == GPARAM || m_e == LPARAM; }
|
||||
bool isSignal() const {
|
||||
return (m_e == WIRE || m_e == WREAL || m_e == IMPLICITWIRE || m_e == TRIWIRE || m_e == TRI0
|
||||
|| m_e == TRI1 || m_e == PORT || m_e == SUPPLY0 || m_e == SUPPLY1 || m_e == VAR);
|
||||
|
|
@ -1168,30 +1183,34 @@ inline std::ostream& operator<<(std::ostream& os, const VNumRange& rhs) {
|
|||
class VUseType final {
|
||||
public:
|
||||
enum en : uint8_t {
|
||||
IMP_INCLUDE, // Implementation (.cpp) needs an include
|
||||
INT_INCLUDE, // Interface (.h) needs an include
|
||||
IMP_FWD_CLASS, // Implementation (.cpp) needs a forward class declaration
|
||||
INT_FWD_CLASS, // Interface (.h) needs a forward class declaration
|
||||
// Enum values are compared with <, so order matters
|
||||
INT_FWD_CLASS = 1 << 0, // Interface (.h) needs a forward class declaration
|
||||
INT_INCLUDE = 1 << 1, // Interface (.h) needs an include
|
||||
};
|
||||
enum en m_e;
|
||||
VUseType()
|
||||
: m_e{IMP_FWD_CLASS} {}
|
||||
: m_e{INT_FWD_CLASS} {}
|
||||
// cppcheck-suppress noExplicitConstructor
|
||||
constexpr VUseType(en _e)
|
||||
: m_e{_e} {}
|
||||
explicit VUseType(int _e)
|
||||
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
|
||||
bool isInclude() const { return m_e == IMP_INCLUDE || m_e == INT_INCLUDE; }
|
||||
bool isFwdClass() const { return m_e == IMP_FWD_CLASS || m_e == INT_FWD_CLASS; }
|
||||
constexpr operator en() const { return m_e; }
|
||||
bool containsAny(VUseType other) { return m_e & other.m_e; }
|
||||
const char* ascii() const {
|
||||
static const char* const names[] = {"IMP_INC", "INT_INC", "IMP_FWD", "INT_FWD"};
|
||||
return names[m_e];
|
||||
static const char* const names[] = {"INT_FWD", "INT_INC", "INT_FWD_INC"};
|
||||
return names[m_e - 1];
|
||||
}
|
||||
};
|
||||
constexpr bool operator==(const VUseType& lhs, const VUseType& rhs) { return lhs.m_e == rhs.m_e; }
|
||||
constexpr bool operator==(const VUseType& lhs, VUseType::en rhs) { return lhs.m_e == rhs; }
|
||||
constexpr bool operator==(VUseType::en lhs, const VUseType& rhs) { return lhs == rhs.m_e; }
|
||||
constexpr VUseType::en operator|(VUseType::en lhs, VUseType::en rhs) {
|
||||
return VUseType::en((uint8_t)lhs | (uint8_t)rhs);
|
||||
}
|
||||
constexpr VUseType::en operator&(VUseType::en lhs, VUseType::en rhs) {
|
||||
return VUseType::en((uint8_t)lhs & (uint8_t)rhs);
|
||||
}
|
||||
inline std::ostream& operator<<(std::ostream& os, const VUseType& rhs) {
|
||||
return os << rhs.ascii();
|
||||
}
|
||||
|
|
@ -1232,6 +1251,44 @@ public:
|
|||
~VBasicTypeKey() = default;
|
||||
};
|
||||
|
||||
// ######################################################################
|
||||
// VSelfPointerText - Represents text to be emitted before a given var reference, call, etc. to
|
||||
// serve as a pointer to a 'self' object. For example, it could be empty (no self pointer), or the
|
||||
// string 'this', or 'vlSymsp->...'
|
||||
|
||||
class VSelfPointerText final {
|
||||
private:
|
||||
// STATIC MEMBERS
|
||||
// Keep these in shared pointers to avoid branching for special cases
|
||||
static const std::shared_ptr<const string> s_emptyp; // Holds ""
|
||||
static const std::shared_ptr<const string> s_thisp; // Holds "this"
|
||||
|
||||
// MEMBERS
|
||||
std::shared_ptr<const string> m_strp;
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
class Empty {}; // for creator type-overload selection
|
||||
VSelfPointerText(Empty)
|
||||
: m_strp{s_emptyp} {}
|
||||
class This {}; // for creator type-overload selection
|
||||
VSelfPointerText(This)
|
||||
: m_strp{s_thisp} {}
|
||||
VSelfPointerText(This, const string& field)
|
||||
: m_strp{std::make_shared<const string>("this->" + field)} {}
|
||||
class VlSyms {}; // for creator type-overload selection
|
||||
VSelfPointerText(VlSyms, const string& field)
|
||||
: m_strp{std::make_shared<const string>("(&vlSymsp->" + field + ')')} {}
|
||||
|
||||
// METHODS
|
||||
bool isEmpty() const { return m_strp == s_emptyp; }
|
||||
bool isVlSym() const { return m_strp->find("vlSymsp") != string::npos; }
|
||||
bool hasThis() const { return m_strp == s_thisp || VString::startsWith(*m_strp, "this"); }
|
||||
string protect(bool useSelfForThis, bool protect) const;
|
||||
const std::string& asString() const { return *m_strp; }
|
||||
bool operator==(const VSelfPointerText& other) const { return *m_strp == *other.m_strp; }
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// AstNUser - Generic base class for AST User nodes.
|
||||
// - Also used to allow parameter passing up/down iterate calls
|
||||
|
|
@ -1314,55 +1371,55 @@ protected:
|
|||
class VNUser1InUse final : VNUserInUseBase {
|
||||
protected:
|
||||
friend class AstNode;
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
public:
|
||||
VNUser1InUse() { allocate(1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser1InUse() { free (1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
VNUser1InUse() { allocate(1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser1InUse() { free (1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void clear() { clearcnt(1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void check() { checkcnt(1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
};
|
||||
class VNUser2InUse final : VNUserInUseBase {
|
||||
protected:
|
||||
friend class AstNode;
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
public:
|
||||
VNUser2InUse() { allocate(2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser2InUse() { free (2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
VNUser2InUse() { allocate(2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser2InUse() { free (2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void clear() { clearcnt(2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void check() { checkcnt(2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
};
|
||||
class VNUser3InUse final : VNUserInUseBase {
|
||||
protected:
|
||||
friend class AstNode;
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
public:
|
||||
VNUser3InUse() { allocate(3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser3InUse() { free (3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
VNUser3InUse() { allocate(3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser3InUse() { free (3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void clear() { clearcnt(3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void check() { checkcnt(3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
};
|
||||
class VNUser4InUse final : VNUserInUseBase {
|
||||
protected:
|
||||
friend class AstNode;
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
public:
|
||||
VNUser4InUse() { allocate(4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser4InUse() { free (4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
VNUser4InUse() { allocate(4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser4InUse() { free (4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void clear() { clearcnt(4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void check() { checkcnt(4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
};
|
||||
class VNUser5InUse final : VNUserInUseBase {
|
||||
protected:
|
||||
friend class AstNode;
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
static uint32_t s_userCntGbl; // Count of which usage of userp() this is
|
||||
static bool s_userBusy; // Count is in use
|
||||
public:
|
||||
VNUser5InUse() { allocate(5, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser5InUse() { free (5, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
VNUser5InUse() { allocate(5, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
~VNUser5InUse() { free (5, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void clear() { clearcnt(5, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
static void check() { checkcnt(5, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
|
||||
};
|
||||
|
|
@ -1645,7 +1702,7 @@ public:
|
|||
const char* typeName() const VL_MT_SAFE { return type().ascii(); } // See also prettyTypeName
|
||||
AstNode* nextp() const VL_MT_STABLE { return m_nextp; }
|
||||
AstNode* backp() const VL_MT_STABLE { return m_backp; }
|
||||
AstNode* abovep() const; // Parent node above, only when no nextp() as otherwise slow
|
||||
AstNode* abovep() const; // Get parent node above, only for list head and tail
|
||||
AstNode* op1p() const VL_MT_STABLE { return m_op1p; }
|
||||
AstNode* op2p() const VL_MT_STABLE { return m_op2p; }
|
||||
AstNode* op3p() const VL_MT_STABLE { return m_op3p; }
|
||||
|
|
@ -1753,74 +1810,74 @@ public:
|
|||
inline bool isString() const VL_MT_STABLE;
|
||||
|
||||
// clang-format off
|
||||
VNUser user1u() const VL_MT_STABLE {
|
||||
VNUser user1u() const VL_MT_STABLE {
|
||||
// Slows things down measurably, so disabled by default
|
||||
//UASSERT_STATIC(VNUser1InUse::s_userBusy, "userp set w/o busy");
|
||||
return ((m_user1Cnt==VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{0});
|
||||
//UASSERT_STATIC(VNUser1InUse::s_userBusy, "user1p used without AstUserInUse");
|
||||
return ((m_user1Cnt == VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{0});
|
||||
}
|
||||
AstNode* user1p() const VL_MT_STABLE { return user1u().toNodep(); }
|
||||
void user1u(const VNUser& user) { m_user1u=user; m_user1Cnt=VNUser1InUse::s_userCntGbl; }
|
||||
void user1p(void* userp) { user1u(VNUser{userp}); }
|
||||
int user1() const { return user1u().toInt(); }
|
||||
void user1(int val) { user1u(VNUser{val}); }
|
||||
int user1Inc(int val=1) { int v=user1(); user1(v+val); return v; }
|
||||
int user1SetOnce() { int v=user1(); if (!v) user1(1); return v; } // Better for cache than user1Inc()
|
||||
AstNode* user1p() const VL_MT_STABLE { return user1u().toNodep(); }
|
||||
void user1u(const VNUser& user) { m_user1u = user; m_user1Cnt = VNUser1InUse::s_userCntGbl; }
|
||||
void user1p(void* userp) { user1u(VNUser{userp}); }
|
||||
void user1(int val) { user1u(VNUser{val}); }
|
||||
int user1() const { return user1u().toInt(); }
|
||||
int user1Inc(int val = 1) { int v = user1(); user1(v + val); return v; }
|
||||
int user1SetOnce() { int v = user1(); if (!v) user1(1); return v; } // Better for cache than user1Inc()
|
||||
static void user1ClearTree() { VNUser1InUse::clear(); } // Clear userp()'s across the entire tree
|
||||
|
||||
VNUser user2u() const VL_MT_STABLE {
|
||||
VNUser user2u() const VL_MT_STABLE {
|
||||
// Slows things down measurably, so disabled by default
|
||||
//UASSERT_STATIC(VNUser2InUse::s_userBusy, "userp set w/o busy");
|
||||
return ((m_user2Cnt==VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{0});
|
||||
//UASSERT_STATIC(VNUser2InUse::s_userBusy, "user2p used without AstUserInUse");
|
||||
return ((m_user2Cnt == VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{0});
|
||||
}
|
||||
AstNode* user2p() const VL_MT_STABLE { return user2u().toNodep(); }
|
||||
void user2u(const VNUser& user) { m_user2u=user; m_user2Cnt=VNUser2InUse::s_userCntGbl; }
|
||||
void user2p(void* userp) { user2u(VNUser{userp}); }
|
||||
int user2() const { return user2u().toInt(); }
|
||||
void user2(int val) { user2u(VNUser{val}); }
|
||||
int user2Inc(int val=1) { int v=user2(); user2(v+val); return v; }
|
||||
int user2SetOnce() { int v=user2(); if (!v) user2(1); return v; } // Better for cache than user2Inc()
|
||||
AstNode* user2p() const VL_MT_STABLE { return user2u().toNodep(); }
|
||||
void user2u(const VNUser& user) { m_user2u = user; m_user2Cnt = VNUser2InUse::s_userCntGbl; }
|
||||
void user2p(void* userp) { user2u(VNUser{userp}); }
|
||||
void user2(int val) { user2u(VNUser{val}); }
|
||||
int user2() const { return user2u().toInt(); }
|
||||
int user2Inc(int val = 1) { int v = user2(); user2(v + val); return v; }
|
||||
int user2SetOnce() { int v = user2(); if (!v) user2(1); return v; } // Better for cache than user2Inc()
|
||||
static void user2ClearTree() { VNUser2InUse::clear(); } // Clear userp()'s across the entire tree
|
||||
|
||||
VNUser user3u() const VL_MT_STABLE {
|
||||
VNUser user3u() const VL_MT_STABLE {
|
||||
// Slows things down measurably, so disabled by default
|
||||
//UASSERT_STATIC(VNUser3InUse::s_userBusy, "userp set w/o busy");
|
||||
return ((m_user3Cnt==VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{0});
|
||||
//UASSERT_STATIC(VNUser3InUse::s_userBusy, "user3p used without AstUserInUse");
|
||||
return ((m_user3Cnt == VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{0});
|
||||
}
|
||||
AstNode* user3p() const VL_MT_STABLE { return user3u().toNodep(); }
|
||||
void user3u(const VNUser& user) { m_user3u=user; m_user3Cnt=VNUser3InUse::s_userCntGbl; }
|
||||
void user3p(void* userp) { user3u(VNUser{userp}); }
|
||||
int user3() const { return user3u().toInt(); }
|
||||
void user3(int val) { user3u(VNUser{val}); }
|
||||
int user3Inc(int val=1) { int v=user3(); user3(v+val); return v; }
|
||||
int user3SetOnce() { int v=user3(); if (!v) user3(1); return v; } // Better for cache than user3Inc()
|
||||
AstNode* user3p() const VL_MT_STABLE { return user3u().toNodep(); }
|
||||
void user3u(const VNUser& user) { m_user3u = user; m_user3Cnt = VNUser3InUse::s_userCntGbl; }
|
||||
void user3p(void* userp) { user3u(VNUser{userp}); }
|
||||
void user3(int val) { user3u(VNUser{val}); }
|
||||
int user3() const { return user3u().toInt(); }
|
||||
int user3Inc(int val = 1) { int v = user3(); user3(v + val); return v; }
|
||||
int user3SetOnce() { int v = user3(); if (!v) user3(1); return v; } // Better for cache than user3Inc()
|
||||
static void user3ClearTree() { VNUser3InUse::clear(); } // Clear userp()'s across the entire tree
|
||||
|
||||
VNUser user4u() const VL_MT_STABLE {
|
||||
VNUser user4u() const VL_MT_STABLE {
|
||||
// Slows things down measurably, so disabled by default
|
||||
//UASSERT_STATIC(VNUser4InUse::s_userBusy, "userp set w/o busy");
|
||||
return ((m_user4Cnt==VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{0});
|
||||
//UASSERT_STATIC(VNUser4InUse::s_userBusy, "user4p used without AstUserInUse");
|
||||
return ((m_user4Cnt == VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{0});
|
||||
}
|
||||
AstNode* user4p() const VL_MT_STABLE { return user4u().toNodep(); }
|
||||
void user4u(const VNUser& user) { m_user4u=user; m_user4Cnt=VNUser4InUse::s_userCntGbl; }
|
||||
void user4p(void* userp) { user4u(VNUser{userp}); }
|
||||
int user4() const { return user4u().toInt(); }
|
||||
void user4(int val) { user4u(VNUser{val}); }
|
||||
int user4Inc(int val=1) { int v=user4(); user4(v+val); return v; }
|
||||
int user4SetOnce() { int v=user4(); if (!v) user4(1); return v; } // Better for cache than user4Inc()
|
||||
AstNode* user4p() const VL_MT_STABLE { return user4u().toNodep(); }
|
||||
void user4u(const VNUser& user) { m_user4u = user; m_user4Cnt = VNUser4InUse::s_userCntGbl; }
|
||||
void user4p(void* userp) { user4u(VNUser{userp}); }
|
||||
void user4(int val) { user4u(VNUser{val}); }
|
||||
int user4() const { return user4u().toInt(); }
|
||||
int user4Inc(int val = 1) { int v = user4(); user4(v + val); return v; }
|
||||
int user4SetOnce() { int v = user4(); if (!v) user4(1); return v; } // Better for cache than user4Inc()
|
||||
static void user4ClearTree() { VNUser4InUse::clear(); } // Clear userp()'s across the entire tree
|
||||
|
||||
VNUser user5u() const VL_MT_STABLE {
|
||||
VNUser user5u() const VL_MT_STABLE {
|
||||
// Slows things down measurably, so disabled by default
|
||||
//UASSERT_STATIC(VNUser5InUse::s_userBusy, "userp set w/o busy");
|
||||
return ((m_user5Cnt==VNUser5InUse::s_userCntGbl) ? m_user5u : VNUser{0});
|
||||
//UASSERT_STATIC(VNUser5InUse::s_userBusy, "user5p used without AstUserInUse");
|
||||
return ((m_user5Cnt == VNUser5InUse::s_userCntGbl) ? m_user5u : VNUser{0});
|
||||
}
|
||||
AstNode* user5p() const VL_MT_STABLE { return user5u().toNodep(); }
|
||||
void user5u(const VNUser& user) { m_user5u=user; m_user5Cnt=VNUser5InUse::s_userCntGbl; }
|
||||
void user5p(void* userp) { user5u(VNUser{userp}); }
|
||||
int user5() const { return user5u().toInt(); }
|
||||
void user5(int val) { user5u(VNUser{val}); }
|
||||
int user5Inc(int val=1) { int v=user5(); user5(v+val); return v; }
|
||||
int user5SetOnce() { int v=user5(); if (!v) user5(1); return v; } // Better for cache than user5Inc()
|
||||
AstNode* user5p() const VL_MT_STABLE { return user5u().toNodep(); }
|
||||
void user5u(const VNUser& user) { m_user5u = user; m_user5Cnt = VNUser5InUse::s_userCntGbl; }
|
||||
void user5p(void* userp) { user5u(VNUser{userp}); }
|
||||
void user5(int val) { user5u(VNUser{val}); }
|
||||
int user5() const { return user5u().toInt(); }
|
||||
int user5Inc(int val = 1) { int v = user5(); user5(v + val); return v; }
|
||||
int user5SetOnce() { int v = user5(); if (!v) user5(1); return v; } // Better for cache than user5Inc()
|
||||
static void user5ClearTree() { VNUser5InUse::clear(); } // Clear userp()'s across the entire tree
|
||||
// clang-format on
|
||||
|
||||
|
|
@ -1901,9 +1958,9 @@ public:
|
|||
static AstBasicDType* findInsertSameDType(AstBasicDType* nodep);
|
||||
|
||||
// METHODS - dump and error
|
||||
void v3errorEnd(std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex);
|
||||
void v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex);
|
||||
void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN
|
||||
VL_REQUIRES(V3Error::s().m_mutex);
|
||||
VL_RELEASE(V3Error::s().m_mutex);
|
||||
string warnContextPrimary() const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
return fileline()->warnContextPrimary();
|
||||
}
|
||||
|
|
@ -1941,10 +1998,6 @@ public:
|
|||
AstNode* belowp); // When calling, "this" is second argument
|
||||
|
||||
// METHODS - Iterate on a tree
|
||||
// Clone or return nullptr if nullptr
|
||||
static AstNode* cloneTreeNull(AstNode* nodep, bool cloneNextLink) {
|
||||
return nodep ? nodep->cloneTree(cloneNextLink) : nullptr;
|
||||
}
|
||||
AstNode* cloneTree(bool cloneNextLink); // Not const, as sets clonep() on original nodep
|
||||
bool gateTree() { return gateTreeIter(); } // Is tree isGateOptimizable?
|
||||
inline bool sameTree(const AstNode* node2p) const; // Does tree of this == node2p?
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ bool AstVarRef::sameNoLvalue(AstVarRef* samep) const {
|
|||
return (varScopep() == samep->varScopep());
|
||||
} else {
|
||||
return (selfPointer() == samep->selfPointer()
|
||||
&& (!selfPointer().empty() || !samep->selfPointer().empty())
|
||||
&& (!selfPointer().isEmpty() || !samep->selfPointer().isEmpty())
|
||||
&& varp()->name() == samep->varp()->name());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -360,14 +360,8 @@ class AstNodeCond VL_NOT_FINAL : public AstNodeTriop {
|
|||
// @astgen alias op2 := thenp
|
||||
// @astgen alias op3 := elsep
|
||||
protected:
|
||||
AstNodeCond(VNType t, FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep)
|
||||
: AstNodeTriop{t, fl, condp, thenp, elsep} {
|
||||
if (thenp) {
|
||||
dtypeFrom(thenp);
|
||||
} else if (elsep) {
|
||||
dtypeFrom(elsep);
|
||||
}
|
||||
}
|
||||
AstNodeCond(VNType t, FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp,
|
||||
AstNodeExpr* elsep);
|
||||
|
||||
public:
|
||||
ASTGEN_MEMBERS_AstNodeCond;
|
||||
|
|
@ -450,8 +444,9 @@ class AstNodeVarRef VL_NOT_FINAL : public AstNodeExpr {
|
|||
AstVar* m_varp; // [AfterLink] Pointer to variable itself
|
||||
AstVarScope* m_varScopep = nullptr; // Varscope for hierarchy
|
||||
AstNodeModule* m_classOrPackagep = nullptr; // Class/package of the variable
|
||||
string m_selfPointer; // Output code object pointer (e.g.: 'this')
|
||||
|
||||
VSelfPointerText m_selfPointer
|
||||
= VSelfPointerText{VSelfPointerText::Empty()}; // Output code object
|
||||
// pointer (e.g.: 'this')
|
||||
protected:
|
||||
AstNodeVarRef(VNType t, FileLine* fl, const VAccess& access)
|
||||
: AstNodeExpr{t, fl}
|
||||
|
|
@ -480,9 +475,11 @@ public:
|
|||
}
|
||||
AstVarScope* varScopep() const { return m_varScopep; }
|
||||
void varScopep(AstVarScope* varscp) { m_varScopep = varscp; }
|
||||
string selfPointer() const { return m_selfPointer; }
|
||||
void selfPointer(const string& value) { m_selfPointer = value; }
|
||||
string selfPointerProtect(bool useSelfForThis) const;
|
||||
const VSelfPointerText& selfPointer() const { return m_selfPointer; }
|
||||
void selfPointer(const VSelfPointerText& selfPointer) { m_selfPointer = selfPointer; }
|
||||
string selfPointerProtect(bool useSelfForThis) const {
|
||||
return selfPointer().protect(useSelfForThis, protect());
|
||||
}
|
||||
AstNodeModule* classOrPackagep() const { return m_classOrPackagep; }
|
||||
void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; }
|
||||
// Know no children, and hot function, so skip iterator for speed
|
||||
|
|
@ -588,21 +585,13 @@ class AstCMethodHard final : public AstNodeExpr {
|
|||
string m_name; // Name of method
|
||||
bool m_pure = false; // Pure optimizable
|
||||
public:
|
||||
AstCMethodHard(FileLine* fl, AstNodeExpr* fromp, VFlagChildDType, const string& name,
|
||||
AstNodeExpr* pinsp = nullptr)
|
||||
: ASTGEN_SUPER_CMethodHard(fl)
|
||||
, m_name{name} {
|
||||
// TODO: this constructor is exactly the same as the other, bar the ignored tag argument
|
||||
this->fromp(fromp);
|
||||
this->addPinsp(pinsp);
|
||||
dtypep(nullptr); // V3Width will resolve
|
||||
}
|
||||
AstCMethodHard(FileLine* fl, AstNodeExpr* fromp, const string& name,
|
||||
AstNodeExpr* pinsp = nullptr)
|
||||
: ASTGEN_SUPER_CMethodHard(fl)
|
||||
, m_name{name} {
|
||||
this->fromp(fromp);
|
||||
this->addPinsp(pinsp);
|
||||
setPurity();
|
||||
}
|
||||
ASTGEN_MEMBERS_AstCMethodHard;
|
||||
string name() const override VL_MT_STABLE { return m_name; } // * = Var name
|
||||
|
|
@ -612,11 +601,13 @@ public:
|
|||
return (m_name == asamep->m_name);
|
||||
}
|
||||
bool isPure() const override { return m_pure; }
|
||||
void pure(bool flag) { m_pure = flag; }
|
||||
int instrCount() const override;
|
||||
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
|
||||
string emitC() override { V3ERROR_NA_RETURN(""); }
|
||||
bool cleanOut() const override { return true; }
|
||||
|
||||
private:
|
||||
void setPurity();
|
||||
};
|
||||
class AstCast final : public AstNodeExpr {
|
||||
// Cast to appropriate data type
|
||||
|
|
@ -1019,6 +1010,34 @@ public:
|
|||
// May return nullptr on parse failure.
|
||||
static AstConst* parseParamLiteral(FileLine* fl, const string& literal);
|
||||
};
|
||||
class AstCvtDynArrayToPacked final : public AstNodeExpr {
|
||||
// Cast from dynamic queue data type to packed array
|
||||
// @astgen op1 := fromp : AstNodeExpr
|
||||
public:
|
||||
AstCvtDynArrayToPacked(FileLine* fl, AstNodeExpr* fromp, AstNodeDType* dtp)
|
||||
: ASTGEN_SUPER_CvtDynArrayToPacked(fl) {
|
||||
this->fromp(fromp);
|
||||
dtypeFrom(dtp);
|
||||
}
|
||||
ASTGEN_MEMBERS_AstCvtDynArrayToPacked;
|
||||
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
|
||||
string emitC() override { V3ERROR_NA_RETURN(""); }
|
||||
bool cleanOut() const override { return true; }
|
||||
};
|
||||
class AstCvtPackedToDynArray final : public AstNodeExpr {
|
||||
// Cast from packed array to dynamic queue data type
|
||||
// @astgen op1 := fromp : AstNodeExpr
|
||||
public:
|
||||
AstCvtPackedToDynArray(FileLine* fl, AstNodeExpr* fromp, AstNodeDType* dtp)
|
||||
: ASTGEN_SUPER_CvtPackedToDynArray(fl) {
|
||||
this->fromp(fromp);
|
||||
dtypeFrom(dtp);
|
||||
}
|
||||
ASTGEN_MEMBERS_AstCvtPackedToDynArray;
|
||||
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
|
||||
string emitC() override { V3ERROR_NA_RETURN(""); }
|
||||
bool cleanOut() const override { return true; }
|
||||
};
|
||||
class AstDot final : public AstNodeExpr {
|
||||
// A dot separating paths in an AstVarXRef, AstFuncRef or AstTaskRef
|
||||
// These are eliminated in the link stage
|
||||
|
|
@ -1102,6 +1121,7 @@ public:
|
|||
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
|
||||
string emitC() override { V3ERROR_NA_RETURN(""); }
|
||||
bool cleanOut() const override { return true; }
|
||||
bool isPure() const override { return false; }
|
||||
bool same(const AstNode*) const override { return true; }
|
||||
};
|
||||
class AstFError final : public AstNodeExpr {
|
||||
|
|
@ -1308,7 +1328,9 @@ public:
|
|||
bool cleanOut() const override { return true; }
|
||||
};
|
||||
class AstImplication final : public AstNodeExpr {
|
||||
// Verilog |-> |=>
|
||||
// Verilog Implication Operator
|
||||
// Nonoverlapping "|=>"
|
||||
// Overlapping "|->" (doesn't currently use this - might make new Ast type)
|
||||
// @astgen op1 := lhsp : AstNodeExpr
|
||||
// @astgen op2 := rhsp : AstNodeExpr
|
||||
// @astgen op3 := sentreep : Optional[AstSenTree]
|
||||
|
|
@ -4023,16 +4045,19 @@ public:
|
|||
// === AstNodeCCall ===
|
||||
class AstCCall final : public AstNodeCCall {
|
||||
// C++ function call
|
||||
string m_selfPointer; // Output code object pointer (e.g.: 'this')
|
||||
|
||||
VSelfPointerText m_selfPointer
|
||||
= VSelfPointerText{VSelfPointerText::Empty()}; // Output code object
|
||||
// pointer (e.g.: 'this')
|
||||
public:
|
||||
AstCCall(FileLine* fl, AstCFunc* funcp, AstNodeExpr* argsp = nullptr)
|
||||
: ASTGEN_SUPER_CCall(fl, funcp, argsp) {}
|
||||
ASTGEN_MEMBERS_AstCCall;
|
||||
|
||||
string selfPointer() const { return m_selfPointer; }
|
||||
void selfPointer(const string& value) { m_selfPointer = value; }
|
||||
string selfPointerProtect(bool useSelfForThis) const;
|
||||
const VSelfPointerText& selfPointer() const { return m_selfPointer; }
|
||||
void selfPointer(const VSelfPointerText& selfPointer) { m_selfPointer = selfPointer; }
|
||||
string selfPointerProtect(bool useSelfForThis) const {
|
||||
return selfPointer().protect(useSelfForThis, protect());
|
||||
}
|
||||
};
|
||||
class AstCMethodCall final : public AstNodeCCall {
|
||||
// C++ method call
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
class AstNodeBlock VL_NOT_FINAL : public AstNode {
|
||||
// A Begin/fork block
|
||||
// @astgen op1 := stmtsp : List[AstNode]
|
||||
// @astgen op2 := stmtsp : List[AstNode]
|
||||
// Parents: statement
|
||||
private:
|
||||
string m_name; // Name of block
|
||||
|
|
@ -81,7 +81,7 @@ private:
|
|||
bool m_isConstructor : 1; // Class constructor
|
||||
bool m_isHideLocal : 1; // Verilog local
|
||||
bool m_isHideProtected : 1; // Verilog protected
|
||||
bool m_pure : 1; // DPI import pure (vs. virtual pure)
|
||||
bool m_dpiPure : 1; // DPI import pure (vs. virtual pure)
|
||||
bool m_pureVirtual : 1; // Pure virtual
|
||||
bool m_recursive : 1; // Recursive or part of recursion
|
||||
bool m_underGenerate : 1; // Under generate (for warning)
|
||||
|
|
@ -107,7 +107,7 @@ protected:
|
|||
, m_isConstructor{false}
|
||||
, m_isHideLocal{false}
|
||||
, m_isHideProtected{false}
|
||||
, m_pure{false}
|
||||
, m_dpiPure{false}
|
||||
, m_pureVirtual{false}
|
||||
, m_recursive{false}
|
||||
, m_underGenerate{false}
|
||||
|
|
@ -119,10 +119,13 @@ protected:
|
|||
|
||||
public:
|
||||
ASTGEN_MEMBERS_AstNodeFTask;
|
||||
virtual AstNodeFTask* cloneType(const string& name) = 0;
|
||||
void dump(std::ostream& str = std::cout) const override;
|
||||
string name() const override VL_MT_STABLE { return m_name; } // * = Var name
|
||||
bool maybePointedTo() const override { return true; }
|
||||
bool isGateOptimizable() const override { return !((m_dpiExport || m_dpiImport) && !m_pure); }
|
||||
bool isGateOptimizable() const override {
|
||||
return !((m_dpiExport || m_dpiImport) && !m_dpiPure);
|
||||
}
|
||||
// {AstFunc only} op1 = Range output variable
|
||||
void name(const string& name) override { m_name = name; }
|
||||
string cname() const { return m_cname; }
|
||||
|
|
@ -162,8 +165,8 @@ public:
|
|||
void isHideLocal(bool flag) { m_isHideLocal = flag; }
|
||||
bool isHideProtected() const { return m_isHideProtected; }
|
||||
void isHideProtected(bool flag) { m_isHideProtected = flag; }
|
||||
void pure(bool flag) { m_pure = flag; }
|
||||
bool pure() const { return m_pure; }
|
||||
void dpiPure(bool flag) { m_dpiPure = flag; }
|
||||
bool dpiPure() const { return m_dpiPure; }
|
||||
void pureVirtual(bool flag) { m_pureVirtual = flag; }
|
||||
bool pureVirtual() const { return m_pureVirtual; }
|
||||
void recursive(bool flag) { m_recursive = flag; }
|
||||
|
|
@ -177,6 +180,15 @@ public:
|
|||
void lifetime(const VLifetime& flag) { m_lifetime = flag; }
|
||||
VLifetime lifetime() const { return m_lifetime; }
|
||||
bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); }
|
||||
void propagateAttrFrom(const AstNodeFTask* fromp) {
|
||||
// Creating a wrapper with e.g. cloneType(); preserve some attributes
|
||||
classMethod(fromp->classMethod());
|
||||
isHideLocal(fromp->isHideLocal());
|
||||
isHideProtected(fromp->isHideProtected());
|
||||
isVirtual(fromp->isVirtual());
|
||||
lifetime(fromp->lifetime());
|
||||
underGenerate(fromp->underGenerate());
|
||||
}
|
||||
};
|
||||
class AstNodeFile VL_NOT_FINAL : public AstNode {
|
||||
// Emitted Output file
|
||||
|
|
@ -578,7 +590,7 @@ private:
|
|||
bool m_isInline : 1; // Inline function
|
||||
bool m_isVirtual : 1; // Virtual function
|
||||
bool m_entryPoint : 1; // User may call into this top level function
|
||||
bool m_pure : 1; // Pure function
|
||||
bool m_dpiPure : 1; // Pure DPI function
|
||||
bool m_dpiContext : 1; // Declared as 'context' DPI import/export function
|
||||
bool m_dpiExportDispatcher : 1; // This is the DPI export entry point (i.e.: called by user)
|
||||
bool m_dpiExportImpl : 1; // DPI export implementation (called from DPI dispatcher via lookup)
|
||||
|
|
@ -607,7 +619,7 @@ public:
|
|||
m_isVirtual = false;
|
||||
m_needProcess = false;
|
||||
m_entryPoint = false;
|
||||
m_pure = false;
|
||||
m_dpiPure = false;
|
||||
m_dpiContext = false;
|
||||
m_dpiExportDispatcher = false;
|
||||
m_dpiExportImpl = false;
|
||||
|
|
@ -678,8 +690,8 @@ public:
|
|||
void setNeedProcess() { m_needProcess = true; }
|
||||
bool entryPoint() const { return m_entryPoint; }
|
||||
void entryPoint(bool flag) { m_entryPoint = flag; }
|
||||
bool pure() const { return m_pure; }
|
||||
void pure(bool flag) { m_pure = flag; }
|
||||
bool dpiPure() const { return m_dpiPure; }
|
||||
void dpiPure(bool flag) { m_dpiPure = flag; }
|
||||
bool dpiContext() const { return m_dpiContext; }
|
||||
void dpiContext(bool flag) { m_dpiContext = flag; }
|
||||
bool dpiExportDispatcher() const VL_MT_SAFE { return m_dpiExportDispatcher; }
|
||||
|
|
@ -699,6 +711,16 @@ public:
|
|||
&& finalsp() == nullptr;
|
||||
}
|
||||
};
|
||||
class AstCLocalScope final : public AstNode {
|
||||
// Pack statements into an unnamed scope when generating C++
|
||||
// @astgen op1 := stmtsp : List[AstNode]
|
||||
public:
|
||||
AstCLocalScope(FileLine* fl, AstNode* stmtsp)
|
||||
: ASTGEN_SUPER_CLocalScope(fl) {
|
||||
this->addStmtsp(stmtsp);
|
||||
}
|
||||
ASTGEN_MEMBERS_AstCLocalScope;
|
||||
};
|
||||
class AstCUse final : public AstNode {
|
||||
// C++ use of a class or #include; indicates need of forward declaration
|
||||
// Parents: NODEMODULE
|
||||
|
|
@ -1378,7 +1400,6 @@ public:
|
|||
void dump(std::ostream& str) const override;
|
||||
bool same(const AstNode* samep) const override;
|
||||
string nameDotless() const;
|
||||
string nameVlSym() const { return string{"vlSymsp->"} + nameDotless(); }
|
||||
AstNodeModule* modp() const { return m_modp; }
|
||||
//
|
||||
AstScope* aboveScopep() const VL_MT_SAFE { return m_aboveScopep; }
|
||||
|
|
@ -1912,9 +1933,7 @@ public:
|
|||
bool isClassMember() const { return varType() == VVarType::MEMBER; }
|
||||
bool isStatementTemp() const { return (varType() == VVarType::STMTTEMP); }
|
||||
bool isXTemp() const { return (varType() == VVarType::XTEMP); }
|
||||
bool isParam() const VL_MT_SAFE {
|
||||
return (varType() == VVarType::LPARAM || varType() == VVarType::GPARAM);
|
||||
}
|
||||
bool isParam() const { return varType().isParam(); }
|
||||
bool isGParam() const { return (varType() == VVarType::GPARAM); }
|
||||
bool isGenVar() const { return (varType() == VVarType::GENVAR); }
|
||||
bool isBitLogic() const {
|
||||
|
|
@ -1955,18 +1974,25 @@ public:
|
|||
string verilogKwd() const override;
|
||||
void lifetime(const VLifetime& flag) { m_lifetime = flag; }
|
||||
VLifetime lifetime() const { return m_lifetime; }
|
||||
void propagateAttrFrom(AstVar* fromp) {
|
||||
void propagateAttrFrom(const AstVar* fromp) {
|
||||
// This is getting connected to fromp; keep attributes
|
||||
// Note the method below too
|
||||
if (fromp->attrFileDescr()) attrFileDescr(true);
|
||||
if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
|
||||
if (fromp->isContinuously()) isContinuously(true);
|
||||
}
|
||||
void propagateWrapAttrFrom(const AstVar* fromp) {
|
||||
// Creating a function wrapper; keep attributes
|
||||
propagateAttrFrom(fromp);
|
||||
direction(fromp->direction());
|
||||
declDirection(fromp->declDirection());
|
||||
lifetime(fromp->lifetime());
|
||||
}
|
||||
bool gateMultiInputOptimizable() const {
|
||||
// Ok to gate optimize; must return false if propagateAttrFrom would do anything
|
||||
return !isUsedClock();
|
||||
}
|
||||
void combineType(AstVar* typevarp) {
|
||||
void combineType(const AstVar* typevarp) {
|
||||
// This is same as typevarp (for combining input & reg decls)
|
||||
// "this" is the input var. typevarp is the reg var.
|
||||
propagateAttrFrom(typevarp);
|
||||
|
|
@ -2040,7 +2066,7 @@ public:
|
|||
class AstBegin final : public AstNodeBlock {
|
||||
// A Begin/end named block, only exists shortly after parsing until linking
|
||||
// Parents: statement
|
||||
// @astgen op2 := genforp : Optional[AstNode]
|
||||
// @astgen op1 := genforp : Optional[AstNode]
|
||||
|
||||
bool m_generate; // Underneath a generate
|
||||
const bool m_implied; // Not inserted by user
|
||||
|
|
@ -2059,6 +2085,7 @@ public:
|
|||
};
|
||||
class AstFork final : public AstNodeBlock {
|
||||
// A fork named block
|
||||
// @astgen op1 := initsp : List[AstNode]
|
||||
// Parents: statement
|
||||
// Children: statements
|
||||
private:
|
||||
|
|
@ -2084,6 +2111,24 @@ public:
|
|||
}
|
||||
ASTGEN_MEMBERS_AstFunc;
|
||||
bool hasDType() const override { return true; }
|
||||
AstNodeFTask* cloneType(const string& name) override {
|
||||
return new AstFunc{fileline(), name, nullptr, nullptr};
|
||||
}
|
||||
};
|
||||
class AstLet final : public AstNodeFTask {
|
||||
// Verilog "let" statement
|
||||
// Parents: MODULE
|
||||
// stmtp is always a StmtExpr as Let always returns AstNodeExpr
|
||||
public:
|
||||
AstLet(FileLine* fl, const string& name)
|
||||
: ASTGEN_SUPER_Let(fl, name, nullptr) {}
|
||||
ASTGEN_MEMBERS_AstLet;
|
||||
bool hasDType() const override { return true; }
|
||||
const char* broken() const override {
|
||||
BROKEN_RTN(!VN_IS(stmtsp(), StmtExpr));
|
||||
return nullptr;
|
||||
}
|
||||
AstNodeFTask* cloneType(const string& name) override { return new AstLet{fileline(), name}; }
|
||||
};
|
||||
class AstProperty final : public AstNodeFTask {
|
||||
// A property inside a module
|
||||
|
|
@ -2092,6 +2137,9 @@ public:
|
|||
: ASTGEN_SUPER_Property(fl, name, stmtp) {}
|
||||
ASTGEN_MEMBERS_AstProperty;
|
||||
bool hasDType() const override { return true; }
|
||||
AstNodeFTask* cloneType(const string& name) override {
|
||||
return new AstProperty{fileline(), name, nullptr};
|
||||
}
|
||||
};
|
||||
class AstTask final : public AstNodeFTask {
|
||||
// A task inside a module
|
||||
|
|
@ -2099,6 +2147,9 @@ public:
|
|||
AstTask(FileLine* fl, const string& name, AstNode* stmtp)
|
||||
: ASTGEN_SUPER_Task(fl, name, stmtp) {}
|
||||
ASTGEN_MEMBERS_AstTask;
|
||||
AstNodeFTask* cloneType(const string& name) override {
|
||||
return new AstTask{fileline(), name, nullptr};
|
||||
}
|
||||
};
|
||||
|
||||
// === AstNodeFile ===
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "V3Hasher.h"
|
||||
#include "V3PartitionGraph.h" // Just for mtask dumping
|
||||
#include "V3String.h"
|
||||
#include "V3Width.h"
|
||||
|
||||
#include "V3Ast__gen_macros.h" // Generated by 'astgen'
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ void AstNodeFTaskRef::cloneRelink() {
|
|||
bool AstNodeFTaskRef::isPure() const {
|
||||
// TODO: For non-DPI functions we could traverse the AST of function's body to determine
|
||||
// pureness.
|
||||
return this->taskp() && this->taskp()->dpiImport() && this->taskp()->pure();
|
||||
return this->taskp() && this->taskp()->dpiImport() && this->taskp()->dpiPure();
|
||||
}
|
||||
|
||||
bool AstNodeFTaskRef::isGateOptimizable() const { return m_taskp && m_taskp->isGateOptimizable(); }
|
||||
|
|
@ -83,12 +84,6 @@ void AstNodeVarRef::cloneRelink() {
|
|||
}
|
||||
}
|
||||
|
||||
string AstNodeVarRef::selfPointerProtect(bool useSelfForThis) const {
|
||||
const string& sp
|
||||
= useSelfForThis ? VString::replaceWord(selfPointer(), "this", "vlSelf") : selfPointer();
|
||||
return VIdProtect::protectWordsIf(sp, protect());
|
||||
}
|
||||
|
||||
void AstAddrOfCFunc::cloneRelink() {
|
||||
if (m_funcp && m_funcp->clonep()) m_funcp = m_funcp->clonep();
|
||||
}
|
||||
|
|
@ -126,14 +121,22 @@ const char* AstNodeCCall::broken() const {
|
|||
BROKEN_RTN(m_funcp && !m_funcp->brokeExists());
|
||||
return nullptr;
|
||||
}
|
||||
bool AstNodeCCall::isPure() const { return funcp()->pure(); }
|
||||
bool AstNodeCCall::isPure() const { return funcp()->dpiPure(); }
|
||||
|
||||
string AstCCall::selfPointerProtect(bool useSelfForThis) const {
|
||||
const string& sp
|
||||
= useSelfForThis ? VString::replaceWord(selfPointer(), "this", "vlSelf") : selfPointer();
|
||||
return VIdProtect::protectWordsIf(sp, protect());
|
||||
AstNodeCond::AstNodeCond(VNType t, FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp,
|
||||
AstNodeExpr* elsep)
|
||||
: AstNodeTriop{t, fl, condp, thenp, elsep} {
|
||||
UASSERT_OBJ(thenp, this, "No thenp expression");
|
||||
UASSERT_OBJ(elsep, this, "No elsep expression");
|
||||
if (thenp->isClassHandleValue() && elsep->isClassHandleValue()) {
|
||||
// Get the most-deriving class type that both arguments can be casted to.
|
||||
AstNodeDType* const commonClassTypep = V3Width::getCommonClassTypep(thenp, elsep);
|
||||
UASSERT_OBJ(commonClassTypep, this, "No common base class exists");
|
||||
dtypep(commonClassTypep);
|
||||
} else {
|
||||
dtypeFrom(thenp);
|
||||
}
|
||||
}
|
||||
|
||||
void AstNodeCond::numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
|
||||
const V3Number& ths) {
|
||||
if (lhs.isNeqZero()) {
|
||||
|
|
@ -2284,7 +2287,7 @@ void AstCFile::dump(std::ostream& str) const {
|
|||
void AstCFunc::dump(std::ostream& str) const {
|
||||
this->AstNode::dump(str);
|
||||
if (slow()) str << " [SLOW]";
|
||||
if (pure()) str << " [PURE]";
|
||||
if (dpiPure()) str << " [DPIPURE]";
|
||||
if (isStatic()) str << " [STATIC]";
|
||||
if (dpiExportDispatcher()) str << " [DPIED]";
|
||||
if (dpiExportImpl()) str << " [DPIEI]";
|
||||
|
|
@ -2314,7 +2317,7 @@ void AstCAwait::dump(std::ostream& str) const {
|
|||
int AstCMethodHard::instrCount() const {
|
||||
if (AstBasicDType* const basicp = fromp()->dtypep()->basicp()) {
|
||||
// TODO: add a more structured description of library methods, rather than using string
|
||||
// matching. See #3715.
|
||||
// matching. See issue #3715.
|
||||
if (basicp->isTriggerVec() && m_name == "word") {
|
||||
// This is an important special case for scheduling so we compute it precisely,
|
||||
// it is simply a load.
|
||||
|
|
@ -2323,6 +2326,74 @@ int AstCMethodHard::instrCount() const {
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
void AstCMethodHard::setPurity() {
|
||||
static const std::map<std::string, bool> isPureMethod{{"andNot", false},
|
||||
{"any", true},
|
||||
{"assign", false},
|
||||
{"at", true},
|
||||
{"atBack", true},
|
||||
{"awaitingCurrentTime", true},
|
||||
{"clear", false},
|
||||
{"clearFired", false},
|
||||
{"commit", false},
|
||||
{"delay", false},
|
||||
{"done", false},
|
||||
{"erase", false},
|
||||
{"evaluate", false},
|
||||
{"evaluation", false},
|
||||
{"exists", true},
|
||||
{"find", true},
|
||||
{"find_first", true},
|
||||
{"find_first_index", true},
|
||||
{"find_index", true},
|
||||
{"find_last", true},
|
||||
{"find_last_index", true},
|
||||
{"fire", false},
|
||||
{"first", false},
|
||||
{"init", false},
|
||||
{"insert", false},
|
||||
{"isFired", true},
|
||||
{"isTriggered", true},
|
||||
{"join", false},
|
||||
{"last", false},
|
||||
{"max", true},
|
||||
{"min", true},
|
||||
{"neq", true},
|
||||
{"next", false},
|
||||
{"pop", false},
|
||||
{"pop_back", false},
|
||||
{"pop_front", false},
|
||||
{"prev", false},
|
||||
{"push", false},
|
||||
{"push_back", false},
|
||||
{"push_front", false},
|
||||
{"r_and", true},
|
||||
{"r_or", true},
|
||||
{"r_product", true},
|
||||
{"r_sum", true},
|
||||
{"r_xor", true},
|
||||
{"renew", false},
|
||||
{"renew_copy", false},
|
||||
{"resume", false},
|
||||
{"reverse", false},
|
||||
{"rsort", false},
|
||||
{"set", false},
|
||||
{"shuffle", false},
|
||||
{"size", true},
|
||||
{"slice", true},
|
||||
{"sliceBackBack", true},
|
||||
{"sliceFrontBack", true},
|
||||
{"sort", false},
|
||||
{"thisOr", false},
|
||||
{"trigger", false},
|
||||
{"unique", true},
|
||||
{"unique_index", true},
|
||||
{"word", true}};
|
||||
|
||||
auto isPureIt = isPureMethod.find(name());
|
||||
UASSERT_OBJ(isPureIt != isPureMethod.end(), this, "Unknown purity of method " + name());
|
||||
m_pure = isPureIt->second;
|
||||
}
|
||||
const char* AstCFunc::broken() const {
|
||||
BROKEN_RTN((m_scopep && !m_scopep->brokeExists()));
|
||||
return nullptr;
|
||||
|
|
|
|||
|
|
@ -147,22 +147,25 @@ bool V3Broken::isLinkable(const AstNode* nodep) { return s_linkableTable.isLinka
|
|||
// Check every node in tree
|
||||
|
||||
class BrokenCheckVisitor final : public VNVisitorConst {
|
||||
bool m_inScope = false; // Under AstScope
|
||||
|
||||
// Constants for marking we are under/not under a node
|
||||
const uint8_t m_brokenCntCurrentNotUnder = s_brokenCntGlobal.get(); // Top bit is clear
|
||||
const uint8_t m_brokenCntCurrentUnder = m_brokenCntCurrentNotUnder | 0x80; // Top bit is set
|
||||
|
||||
// Current CFunc, if any
|
||||
const AstCFunc* m_cfuncp = nullptr;
|
||||
// STATE - across all visitors
|
||||
// All local variables declared in current function
|
||||
std::unordered_set<const AstVar*> m_localVars;
|
||||
std::set<const AstVar*> m_localVars;
|
||||
// Variable references in current function that do not reference an in-scope local
|
||||
std::unordered_map<const AstVar*, const AstNodeVarRef*> m_suspectRefs;
|
||||
std::map<const AstVar*, const AstNodeVarRef*> m_suspectRefs;
|
||||
// Local variables declared in the scope of the current statement
|
||||
std::vector<std::unordered_set<const AstVar*>> m_localsStack;
|
||||
|
||||
// STATE - for current visit position (use VL_RESTORER)
|
||||
const AstCFunc* m_cfuncp = nullptr; // Current CFunc, if any
|
||||
bool m_inScope = false; // Under AstScope
|
||||
std::set<std::string> m_cFuncNames; // CFunc by name in current class/module
|
||||
|
||||
private:
|
||||
// METHODS
|
||||
static void checkWidthMin(const AstNode* nodep) {
|
||||
UASSERT_OBJ(nodep->width() == nodep->widthMin()
|
||||
|| v3Global.widthMinUsage() != VWidthMinUsage::MATCHES_WIDTH,
|
||||
|
|
@ -220,6 +223,7 @@ private:
|
|||
}
|
||||
return false;
|
||||
}
|
||||
// VISITORS
|
||||
void visit(AstNodeAssign* nodep) override {
|
||||
processAndIterate(nodep);
|
||||
UASSERT_OBJ(!(v3Global.assertDTypesResolved() && nodep->brokeLhsMustBeLvalue()
|
||||
|
|
@ -235,10 +239,20 @@ private:
|
|||
}
|
||||
void visit(AstScope* nodep) override {
|
||||
VL_RESTORER(m_inScope);
|
||||
{
|
||||
m_inScope = true;
|
||||
processAndIterate(nodep);
|
||||
}
|
||||
m_inScope = true;
|
||||
VL_RESTORER(m_cFuncNames);
|
||||
m_cFuncNames.clear();
|
||||
processAndIterate(nodep);
|
||||
}
|
||||
void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_cFuncNames);
|
||||
m_cFuncNames.clear();
|
||||
processAndIterate(nodep);
|
||||
}
|
||||
void visit(AstNodeUOrStructDType* nodep) override {
|
||||
VL_RESTORER(m_cFuncNames);
|
||||
m_cFuncNames.clear();
|
||||
processAndIterate(nodep);
|
||||
}
|
||||
void visit(AstNodeVarRef* nodep) override {
|
||||
processAndIterate(nodep);
|
||||
|
|
@ -260,12 +274,19 @@ private:
|
|||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
UASSERT_OBJ(!m_cfuncp, nodep, "Nested AstCFunc");
|
||||
VL_RESTORER(m_cfuncp);
|
||||
m_cfuncp = nodep;
|
||||
m_localVars.clear();
|
||||
m_suspectRefs.clear();
|
||||
m_localsStack.clear();
|
||||
pushLocalScope();
|
||||
|
||||
// Check for duplicate names, otherwise linker might just ignore them
|
||||
string nameArgs = nodep->name();
|
||||
if (!nodep->argTypes().empty()) nameArgs += "(" + nodep->argTypes() + ")";
|
||||
UASSERT_OBJ(m_cFuncNames.emplace(nameArgs).second, nodep,
|
||||
"Duplicate cfunc name: '" << nameArgs << "'");
|
||||
|
||||
processAndIterate(nodep);
|
||||
|
||||
// Check suspect references are all to non-locals
|
||||
|
|
@ -273,8 +294,6 @@ private:
|
|||
UASSERT_OBJ(m_localVars.count(pair.first) == 0, pair.second,
|
||||
"Local variable not in scope where referenced: " << pair.first);
|
||||
}
|
||||
|
||||
m_cfuncp = nullptr;
|
||||
}
|
||||
void visit(AstNodeIf* nodep) override {
|
||||
// Each branch is a separate local variable scope
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ public:
|
|||
callp->argTypes("vlSymsp");
|
||||
} else {
|
||||
if (m_type.isCoverage()) callp->argTypes("first");
|
||||
callp->selfPointer("this");
|
||||
callp->selfPointer(VSelfPointerText{VSelfPointerText::This()});
|
||||
}
|
||||
rootFuncp->addStmtsp(callp->makeStmt());
|
||||
}
|
||||
|
|
@ -229,7 +229,7 @@ void V3CCtors::evalAsserts() {
|
|||
// if (signal & CONST(upper_non_clean_mask)) { fail; }
|
||||
AstVarRef* const vrefp
|
||||
= new AstVarRef{varp->fileline(), varp, VAccess::READ};
|
||||
vrefp->selfPointer("this");
|
||||
vrefp->selfPointer(VSelfPointerText{VSelfPointerText::This()});
|
||||
AstNodeExpr* newp = vrefp;
|
||||
if (varp->isWide()) {
|
||||
newp = new AstWordSel{
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@
|
|||
#include "V3CUse.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3FileLine.h"
|
||||
#include "V3Global.h"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
|
|
@ -46,52 +48,31 @@ class CUseVisitor final : public VNVisitor {
|
|||
|
||||
// MEMBERS
|
||||
AstNodeModule* const m_modp; // Current module
|
||||
std::set<std::pair<VUseType, std::string>> m_didUse; // What we already used
|
||||
bool m_dtypesImplOnly = false;
|
||||
std::map<std::string, std::pair<FileLine*, VUseType>> m_didUse; // What we already used
|
||||
|
||||
// METHODS
|
||||
void addNewUse(AstNode* nodep, VUseType useType, const string& name) {
|
||||
if (m_dtypesImplOnly
|
||||
&& (useType == VUseType::INT_INCLUDE || useType == VUseType::INT_FWD_CLASS))
|
||||
return;
|
||||
|
||||
if (m_didUse.emplace(useType, name).second) {
|
||||
AstCUse* const newp = new AstCUse{nodep->fileline(), useType, name};
|
||||
m_modp->addStmtsp(newp);
|
||||
UINFO(8, "Insert " << newp << endl);
|
||||
auto e = m_didUse.emplace(name, std::make_pair(nodep->fileline(), useType));
|
||||
if (e.second || ((e.first->second.second & useType) != useType)) {
|
||||
e.first->second.second = e.first->second.second | useType;
|
||||
}
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstClassRefDType* nodep) override {
|
||||
if (nodep->user1SetOnce()) return; // Process once
|
||||
addNewUse(nodep, VUseType::INT_FWD_CLASS, nodep->classp()->name());
|
||||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
if (nodep->user1SetOnce()) return; // Process once
|
||||
if (nodep->user1SetOnce()) return;
|
||||
iterateAndNextNull(nodep->argsp());
|
||||
|
||||
{
|
||||
VL_RESTORER(m_dtypesImplOnly);
|
||||
m_dtypesImplOnly = true;
|
||||
|
||||
iterateAndNextNull(nodep->initsp());
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
iterateAndNextNull(nodep->finalsp());
|
||||
}
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
}
|
||||
void visit(AstCCall* nodep) override { return; }
|
||||
void visit(AstCReturn* nodep) override {
|
||||
if (nodep->user1SetOnce()) return; // Process once
|
||||
if (m_dtypesImplOnly) {
|
||||
for (AstNode* exprp = nodep->op1p(); exprp; exprp = exprp->nextp()) {
|
||||
if (exprp->dtypep()) iterate(exprp->dtypep());
|
||||
}
|
||||
} else {
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
UASSERT(!nodep->user1SetOnce(), "Visited same return twice.");
|
||||
iterate(nodep->lhsp()->dtypep());
|
||||
}
|
||||
void visit(AstNodeDType* nodep) override {
|
||||
if (nodep->user1SetOnce()) return; // Process once
|
||||
if (nodep->virtRefDTypep()) iterate(nodep->virtRefDTypep());
|
||||
if (nodep->virtRefDType2p()) iterate(nodep->virtRefDType2p());
|
||||
|
||||
|
|
@ -106,7 +87,7 @@ class CUseVisitor final : public VNVisitor {
|
|||
}
|
||||
void visit(AstNode* nodep) override {
|
||||
if (nodep->user1SetOnce()) return; // Process once
|
||||
if (nodep->dtypep() && !nodep->dtypep()->user1()) iterate(nodep->dtypep());
|
||||
if (nodep->dtypep()) iterate(nodep->dtypep());
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstCell* nodep) override {
|
||||
|
|
@ -121,6 +102,12 @@ public:
|
|||
explicit CUseVisitor(AstNodeModule* modp)
|
||||
: m_modp(modp) {
|
||||
iterate(modp);
|
||||
|
||||
for (auto& used : m_didUse) {
|
||||
AstCUse* const newp = new AstCUse{used.second.first, used.second.second, used.first};
|
||||
m_modp->addStmtsp(newp);
|
||||
UINFO(8, "Insert " << newp << endl);
|
||||
}
|
||||
}
|
||||
~CUseVisitor() override = default;
|
||||
VL_UNCOPYABLE(CUseVisitor);
|
||||
|
|
|
|||
123
src/V3Case.cpp
123
src/V3Case.cpp
|
|
@ -139,32 +139,42 @@ private:
|
|||
std::array<AstNode*, 1 << CASE_OVERLAP_WIDTH> m_valueItem;
|
||||
|
||||
// METHODS
|
||||
bool caseIsEnumComplete(AstCase* nodep, uint32_t numCases) {
|
||||
// Return true if case is across an enum, and every value in the case
|
||||
// statement corresponds to one of the enum values
|
||||
if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return false;
|
||||
AstEnumDType* const enumDtp
|
||||
//! Determine whether we should check case items are complete
|
||||
//! @return Enum's dtype if should check, nullptr if shouldn't
|
||||
const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) {
|
||||
if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr;
|
||||
const AstEnumDType* const enumDtp
|
||||
= VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
|
||||
if (!enumDtp) return false; // Case isn't enum
|
||||
AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
|
||||
if (!basicp) return false; // Not simple type (perhaps IEEE illegal)
|
||||
if (basicp->width() > 32) return false;
|
||||
// Find all case values into a set
|
||||
std::set<uint32_t> caseSet;
|
||||
for (uint32_t i = 0; i < numCases; ++i) { // All case items
|
||||
if (m_valueItem[i]) caseSet.emplace(i);
|
||||
}
|
||||
// Find all enum values into a set
|
||||
std::set<uint32_t> enumSet;
|
||||
for (AstEnumItem* itemp = enumDtp->itemsp(); itemp;
|
||||
if (!enumDtp) return nullptr; // Case isn't enum
|
||||
const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
|
||||
if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal)
|
||||
if (basicp->width() > 32) return nullptr;
|
||||
return enumDtp;
|
||||
}
|
||||
//! @return True if case items are complete, false if there are uncovered enums
|
||||
bool checkCaseEnumComplete(const AstCase* const nodep, const AstEnumDType* const dtype) {
|
||||
const uint32_t numCases = 1UL << m_caseWidth;
|
||||
for (AstEnumItem* itemp = dtype->itemsp(); itemp;
|
||||
itemp = VN_AS(itemp->nextp(), EnumItem)) {
|
||||
AstConst* const econstp = VN_AS(itemp->valuep(), Const);
|
||||
const uint32_t val = econstp->toUInt();
|
||||
// UINFO(9, "Complete enum item " << val << ": " << itemp << endl);
|
||||
enumSet.emplace(val);
|
||||
V3Number nummask{itemp, econstp->width()};
|
||||
nummask.opBitsNonX(econstp->num());
|
||||
const uint32_t mask = nummask.toUInt();
|
||||
V3Number numval{itemp, econstp->width()};
|
||||
numval.opBitsOne(econstp->num());
|
||||
const uint32_t val = numval.toUInt();
|
||||
|
||||
for (uint32_t i = 0; i < numCases; ++i) {
|
||||
if ((i & mask) == val) {
|
||||
if (!m_valueItem[i]) {
|
||||
nodep->v3warn(CASEINCOMPLETE, "Enum item " << itemp->prettyNameQ()
|
||||
<< " not covered by case\n");
|
||||
return false; // enum has uncovered value by case items
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If sets match, all covered
|
||||
return (caseSet == enumSet);
|
||||
return true; // enum is fully covered
|
||||
}
|
||||
bool isCaseTreeFast(AstCase* nodep) {
|
||||
int width = 0;
|
||||
|
|
@ -193,6 +203,8 @@ private:
|
|||
// We can cheat and use uint32_t's because we only support narrow case's
|
||||
bool reportedOverlap = false;
|
||||
bool reportedSubcase = false;
|
||||
bool hasDefaultCase = false;
|
||||
std::map<AstNode*, AstCaseItem*> caseItemMap; // case condition -> case item
|
||||
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
||||
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
||||
for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) {
|
||||
|
|
@ -202,6 +214,7 @@ private:
|
|||
if (neverItem(nodep, iconstp)) {
|
||||
// X in casez can't ever be executed
|
||||
} else {
|
||||
const bool isCondWildcard = iconstp->num().isAnyXZ();
|
||||
V3Number nummask{itemp, iconstp->width()};
|
||||
nummask.opBitsNonX(iconstp->num());
|
||||
const uint32_t mask = nummask.toUInt();
|
||||
|
|
@ -210,16 +223,17 @@ private:
|
|||
const uint32_t val = numval.toUInt();
|
||||
|
||||
uint32_t firstOverlap = 0;
|
||||
bool foundOverlap = false;
|
||||
AstNode* overlappedCondp = nullptr;
|
||||
bool foundHit = false;
|
||||
for (uint32_t i = 0; i < numCases; ++i) {
|
||||
if ((i & mask) == val) {
|
||||
if (!m_valueItem[i]) {
|
||||
m_valueItem[i] = itemp;
|
||||
m_valueItem[i] = icondp;
|
||||
caseItemMap[icondp] = itemp;
|
||||
foundHit = true;
|
||||
} else if (!foundOverlap) {
|
||||
} else if (!overlappedCondp) {
|
||||
firstOverlap = i;
|
||||
foundOverlap = true;
|
||||
overlappedCondp = m_valueItem[i];
|
||||
m_caseNoOverlapsAllCovered = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -227,9 +241,19 @@ private:
|
|||
if (!nodep->priorityPragma()) {
|
||||
// If this case statement doesn't have the priority
|
||||
// keyword, we want to warn on any overlap.
|
||||
if (!reportedOverlap && foundOverlap) {
|
||||
icondp->v3warn(CASEOVERLAP, "Case values overlap (example pattern 0x"
|
||||
<< std::hex << firstOverlap << ")");
|
||||
if (!reportedOverlap && overlappedCondp) {
|
||||
std::ostringstream examplePattern;
|
||||
if (isCondWildcard) {
|
||||
examplePattern << " (example pattern 0x" << std::hex
|
||||
<< firstOverlap << ")";
|
||||
}
|
||||
icondp->v3warn(CASEOVERLAP,
|
||||
"Case conditions overlap"
|
||||
<< examplePattern.str() << "\n"
|
||||
<< icondp->warnContextPrimary() << '\n'
|
||||
<< overlappedCondp->warnOther()
|
||||
<< "... Location of overlapping condition\n"
|
||||
<< overlappedCondp->warnContextSecondary());
|
||||
reportedOverlap = true;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -240,7 +264,11 @@ private:
|
|||
if (!reportedSubcase && !foundHit) {
|
||||
icondp->v3warn(CASEOVERLAP,
|
||||
"Case item ignored: every matching value is covered "
|
||||
"by an earlier item");
|
||||
"by an earlier condition\n"
|
||||
<< icondp->warnContextPrimary() << '\n'
|
||||
<< overlappedCondp->warnOther()
|
||||
<< "... Location of previous condition\n"
|
||||
<< overlappedCondp->warnContextPrimary());
|
||||
reportedSubcase = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -251,17 +279,28 @@ private:
|
|||
for (uint32_t i = 0; i < numCases; ++i) {
|
||||
if (!m_valueItem[i]) m_valueItem[i] = itemp;
|
||||
}
|
||||
caseItemMap[itemp] = itemp;
|
||||
hasDefaultCase = true;
|
||||
}
|
||||
}
|
||||
if (!caseIsEnumComplete(nodep, numCases)) {
|
||||
for (uint32_t i = 0; i < numCases; ++i) {
|
||||
if (!m_valueItem[i]) {
|
||||
nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered "
|
||||
"(example pattern 0x"
|
||||
<< std::hex << i << ")");
|
||||
if (!hasDefaultCase) {
|
||||
const AstEnumDType* const dtype = getEnumCompletionCheckDType(nodep);
|
||||
if (dtype) {
|
||||
if (!checkCaseEnumComplete(nodep, dtype)) {
|
||||
// checkCaseEnumComplete has already warned of incompletion
|
||||
m_caseNoOverlapsAllCovered = false;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < numCases; ++i) {
|
||||
if (!m_valueItem[i]) { // has uncovered case
|
||||
nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered "
|
||||
"(example pattern 0x"
|
||||
<< std::hex << i << ")");
|
||||
m_caseNoOverlapsAllCovered = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274,8 +313,10 @@ private:
|
|||
// Convert valueItem from AstCaseItem* to the expression
|
||||
// Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch
|
||||
for (uint32_t i = 0; i < numCases; ++i) {
|
||||
if (AstCaseItem* const itemp = VN_AS(m_valueItem[i], CaseItem)) {
|
||||
m_valueItem[i] = itemp->stmtsp();
|
||||
if (AstNode* const condp = m_valueItem[i]) {
|
||||
AstCaseItem* caseItemp = caseItemMap[condp];
|
||||
UASSERT(caseItemp, "caseItemp should exist");
|
||||
m_valueItem[i] = caseItemp->stmtsp();
|
||||
}
|
||||
}
|
||||
return true; // All is fine
|
||||
|
|
@ -543,10 +584,12 @@ private:
|
|||
}
|
||||
}
|
||||
//--------------------
|
||||
void visit(AstNode* nodep) override {
|
||||
if (VN_IS(nodep, Always)) m_alwaysp = nodep;
|
||||
void visit(AstAlways* nodep) override {
|
||||
VL_RESTORER(m_alwaysp)
|
||||
m_alwaysp = nodep;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
|
|
|
|||
|
|
@ -1799,7 +1799,7 @@ private:
|
|||
UASSERT_OBJ((rstart + rwidth) == lstart, nodep,
|
||||
"tried to merge two selects which are not adjacent");
|
||||
AstSel* const newselp = new AstSel{
|
||||
lselp->fromp()->fileline(), rselp->fromp()->cloneTree(false), rstart, lwidth + rwidth};
|
||||
lselp->fromp()->fileline(), rselp->fromp()->unlinkFrBack(), rstart, lwidth + rwidth};
|
||||
UINFO(5, "merged two adjacent sel " << lselp << " and " << rselp << " to one " << newselp
|
||||
<< endl);
|
||||
|
||||
|
|
@ -1809,6 +1809,7 @@ private:
|
|||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
void replaceConcatMerge(AstConcat* nodep) {
|
||||
// {llp OP lrp, rlp OP rrp} => {llp, rlp} OP {lrp, rrp}, where OP = AND/OR/XOR
|
||||
AstNodeBiop* const lp = VN_AS(nodep->lhsp(), NodeBiop);
|
||||
AstNodeBiop* const rp = VN_AS(nodep->rhsp(), NodeBiop);
|
||||
AstNodeExpr* const llp = lp->lhsp()->cloneTree(false);
|
||||
|
|
@ -2052,7 +2053,7 @@ private:
|
|||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
return true;
|
||||
}
|
||||
} else if (m_doV && VN_IS(nodep->lhsp(), Concat)) {
|
||||
} else if (m_doV && VN_IS(nodep->lhsp(), Concat) && nodep->isTreePureRecurse()) {
|
||||
bool need_temp = false;
|
||||
if (m_warn && !VN_IS(nodep, AssignDly)) { // Is same var on LHS and RHS?
|
||||
// Note only do this (need user4) when m_warn, which is
|
||||
|
|
@ -2145,25 +2146,29 @@ private:
|
|||
return true;
|
||||
} else if (m_doV && VN_IS(nodep->rhsp(), StreamR)) {
|
||||
// The right-streaming operator on rhs of assignment does not
|
||||
// change the order of bits. Eliminate stream but keep its lhsp
|
||||
// Unlink the stuff
|
||||
AstNodeExpr* const srcp = VN_AS(nodep->rhsp(), StreamR)->lhsp()->unlinkFrBack();
|
||||
AstNode* const sizep = VN_AS(nodep->rhsp(), StreamR)->rhsp()->unlinkFrBack();
|
||||
AstNodeExpr* const streamp = VN_AS(nodep->rhsp(), StreamR)->unlinkFrBack();
|
||||
// change the order of bits. Eliminate stream but keep its lhsp.
|
||||
// Add a cast if needed.
|
||||
AstStreamR* const streamp = VN_AS(nodep->rhsp(), StreamR)->unlinkFrBack();
|
||||
AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack();
|
||||
AstNodeDType* const srcDTypep = srcp->dtypep();
|
||||
if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) {
|
||||
if (nodep->lhsp()->widthMin() > 64) {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Assignment of stream of dynamic "
|
||||
"array to a variable of size greater than 64");
|
||||
}
|
||||
srcp = new AstCvtDynArrayToPacked{srcp->fileline(), srcp, srcDTypep};
|
||||
}
|
||||
nodep->rhsp(srcp);
|
||||
// Cleanup
|
||||
VL_DO_DANGLING(sizep->deleteTree(), sizep);
|
||||
VL_DO_DANGLING(streamp->deleteTree(), streamp);
|
||||
// Further reduce, any of the nodes may have more reductions.
|
||||
return true;
|
||||
} else if (m_doV && VN_IS(nodep->lhsp(), StreamL)) {
|
||||
// Push the stream operator to the rhs of the assignment statement
|
||||
const int dWidth = VN_AS(nodep->lhsp(), StreamL)->lhsp()->width();
|
||||
const int sWidth = nodep->rhsp()->width();
|
||||
// Unlink the stuff
|
||||
AstNodeExpr* const dstp = VN_AS(nodep->lhsp(), StreamL)->lhsp()->unlinkFrBack();
|
||||
AstNodeExpr* streamp = VN_AS(nodep->lhsp(), StreamL)->unlinkFrBack();
|
||||
AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack();
|
||||
AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack();
|
||||
AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack();
|
||||
const int sWidth = srcp->width();
|
||||
const int dWidth = dstp->width();
|
||||
// Connect the rhs to the stream operator and update its width
|
||||
VN_AS(streamp, StreamL)->lhsp(srcp);
|
||||
if (VN_IS(srcp->dtypep(), DynArrayDType) || VN_IS(srcp->dtypep(), QueueDType)
|
||||
|
|
@ -2172,11 +2177,11 @@ private:
|
|||
} else {
|
||||
streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), VSigning::UNSIGNED);
|
||||
}
|
||||
// Shrink the RHS if necessary
|
||||
if (sWidth > dWidth) {
|
||||
if (dWidth == 0) {
|
||||
streamp = new AstCvtPackedToDynArray{nodep->fileline(), streamp, dstp->dtypep()};
|
||||
} else if (sWidth > dWidth) {
|
||||
streamp = new AstSel{streamp->fileline(), streamp, sWidth - dWidth, dWidth};
|
||||
}
|
||||
// Link the nodes back in
|
||||
nodep->lhsp(dstp);
|
||||
nodep->rhsp(streamp);
|
||||
return true;
|
||||
|
|
@ -2184,23 +2189,35 @@ private:
|
|||
// The right stream operator on lhs of assignment statement does
|
||||
// not reorder bits. However, if the rhs is wider than the lhs,
|
||||
// then we select bits from the left-most, not the right-most.
|
||||
const int dWidth = VN_AS(nodep->lhsp(), StreamR)->lhsp()->width();
|
||||
const int sWidth = nodep->rhsp()->width();
|
||||
// Unlink the stuff
|
||||
AstNodeExpr* const dstp = VN_AS(nodep->lhsp(), StreamR)->lhsp()->unlinkFrBack();
|
||||
AstNode* const sizep = VN_AS(nodep->lhsp(), StreamR)->rhsp()->unlinkFrBack();
|
||||
AstNodeExpr* const streamp = VN_AS(nodep->lhsp(), StreamR)->unlinkFrBack();
|
||||
AstNodeExpr* const streamp = nodep->lhsp()->unlinkFrBack();
|
||||
AstNodeExpr* const dstp = VN_AS(streamp, StreamR)->lhsp()->unlinkFrBack();
|
||||
AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack();
|
||||
if (sWidth > dWidth) {
|
||||
const int sWidth = srcp->width();
|
||||
const int dWidth = dstp->width();
|
||||
if (dWidth == 0) {
|
||||
srcp = new AstCvtPackedToDynArray{nodep->fileline(), srcp, dstp->dtypep()};
|
||||
} else if (sWidth > dWidth) {
|
||||
srcp = new AstSel{streamp->fileline(), srcp, sWidth - dWidth, dWidth};
|
||||
}
|
||||
nodep->lhsp(dstp);
|
||||
nodep->rhsp(srcp);
|
||||
// Cleanup
|
||||
VL_DO_DANGLING(sizep->deleteTree(), sizep);
|
||||
VL_DO_DANGLING(streamp->deleteTree(), streamp);
|
||||
// Further reduce, any of the nodes may have more reductions.
|
||||
return true;
|
||||
} else if (m_doV && VN_IS(nodep->rhsp(), StreamL)) {
|
||||
AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep();
|
||||
AstStreamL* streamp = VN_AS(nodep->rhsp(), StreamL);
|
||||
AstNodeExpr* const srcp = streamp->lhsp();
|
||||
const AstNodeDType* const srcDTypep = srcp->dtypep();
|
||||
if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) {
|
||||
if (lhsDtypep->widthMin() > 64) {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Assignment of stream of dynamic "
|
||||
"array to a variable of size greater than 64");
|
||||
}
|
||||
srcp->unlinkFrBack();
|
||||
streamp->lhsp(new AstCvtDynArrayToPacked{srcp->fileline(), srcp, lhsDtypep});
|
||||
streamp->dtypeFrom(lhsDtypep);
|
||||
}
|
||||
} else if (m_doV && replaceAssignMultiSel(nodep)) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -2288,6 +2305,13 @@ private:
|
|||
iterateChildren(nodep);
|
||||
}
|
||||
}
|
||||
void visit(AstCLocalScope* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
if (!nodep->stmtsp()) {
|
||||
nodep->unlinkFrBack();
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
}
|
||||
void visit(AstScope* nodep) override {
|
||||
// No ASSIGNW removals under scope, we've long eliminated INITIALs
|
||||
VL_RESTORER(m_wremove);
|
||||
|
|
@ -3180,8 +3204,17 @@ private:
|
|||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
void visit(AstFuncRef* nodep) override {
|
||||
void visit(AstNodeCCall* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
m_hasJumpDelay = true; // As don't analyze inside tasks for timing controls
|
||||
}
|
||||
void visit(AstNodeFTaskRef* nodep) override {
|
||||
// Note excludes AstFuncRef as other visitor below
|
||||
iterateChildren(nodep);
|
||||
m_hasJumpDelay = true; // As don't analyze inside tasks for timing controls
|
||||
}
|
||||
void visit(AstFuncRef* nodep) override {
|
||||
visit(static_cast<AstNodeFTaskRef*>(nodep));
|
||||
if (m_params) { // Only parameters force us to do constant function call propagation
|
||||
replaceWithSimulation(nodep);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,7 +288,6 @@ private:
|
|||
VL_RESTORER(m_sideEffect);
|
||||
m_inAssign = true;
|
||||
m_sideEffect = false;
|
||||
if (assignInAssign) m_sideEffect = true;
|
||||
iterateAndNextNull(nodep->rhsp());
|
||||
checkAll(nodep);
|
||||
// Has to be direct assignment without any EXTRACTing.
|
||||
|
|
|
|||
|
|
@ -551,7 +551,9 @@ private:
|
|||
}
|
||||
if (!lhsp->backp()) VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
|
||||
} else {
|
||||
iterateChildren(nodep);
|
||||
iterate(nodep->lhsp());
|
||||
m_inDly = false;
|
||||
iterate(nodep->rhsp());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -623,6 +625,12 @@ private:
|
|||
m_inLoop = true;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstExprStmt* nodep) override {
|
||||
VL_RESTORER(m_inDly);
|
||||
// Restoring is needed, because AstExprStmt may contain assignments
|
||||
m_inDly = false;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
//--------------------
|
||||
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ private:
|
|||
|
||||
// TYPES
|
||||
using FuncMmap = std::multimap<std::string, AstCFunc*>;
|
||||
struct ScopeSelfPtr final {
|
||||
VSelfPointerText thisPtr = VSelfPointerText{VSelfPointerText::Empty()};
|
||||
VSelfPointerText vlSymsPtr = VSelfPointerText{VSelfPointerText::Empty()};
|
||||
};
|
||||
|
||||
// STATE
|
||||
AstNodeModule* m_modp = nullptr; // Current module
|
||||
|
|
@ -53,6 +57,7 @@ private:
|
|||
const AstCFunc* m_funcp = nullptr; // Current function
|
||||
bool m_modSingleton = false; // m_modp is only instantiated once
|
||||
FuncMmap m_modFuncs; // Name of public functions added
|
||||
std::map<const AstScope*, ScopeSelfPtr> m_scopeToSelf; // Scope to self pointers
|
||||
|
||||
// METHODS
|
||||
|
||||
|
|
@ -68,12 +73,32 @@ private:
|
|||
return (instances == 1);
|
||||
}
|
||||
|
||||
// Construct a 'this' self pointer for the given scope
|
||||
VSelfPointerText scopeThis(const AstScope* scopep) {
|
||||
auto& ret = m_scopeToSelf[scopep];
|
||||
if (ret.thisPtr.isEmpty()) {
|
||||
string name = scopep->name();
|
||||
string::size_type pos;
|
||||
if ((pos = name.rfind('.')) != string::npos) name.erase(0, pos + 1);
|
||||
ret.thisPtr = VSelfPointerText{VSelfPointerText::This(), name};
|
||||
}
|
||||
return ret.thisPtr;
|
||||
}
|
||||
// Construct a 'vlSyms' self pointer for the given scope
|
||||
VSelfPointerText scopeVlSyms(const AstScope* scopep) {
|
||||
auto& ret = m_scopeToSelf[scopep];
|
||||
if (ret.vlSymsPtr.isEmpty()) {
|
||||
ret.vlSymsPtr = VSelfPointerText{VSelfPointerText::VlSyms(), scopep->nameDotless()};
|
||||
}
|
||||
return ret.vlSymsPtr;
|
||||
}
|
||||
|
||||
// Construct the best self pointer to reference an object in 'scopep' from a CFunc in
|
||||
// 'm_scopep'. Result may be relative ("this->[...]") or absolute ("vlSyms->[...]").
|
||||
//
|
||||
// Using relative references allows V3Combine'ing code across multiple instances of the same
|
||||
// module.
|
||||
string descopedSelfPointer(const AstScope* scopep) {
|
||||
VSelfPointerText descopedSelfPointer(const AstScope* scopep) {
|
||||
UASSERT(scopep, "Var/Func not scoped");
|
||||
// Static functions can't use relative references via 'this->'
|
||||
const bool relativeRefOk = !m_funcp->isStatic();
|
||||
|
|
@ -85,23 +110,20 @@ private:
|
|||
if (VN_IS(scopep->modp(), Class)) {
|
||||
// Direct reference to class members are from within the class itself, references from
|
||||
// outside the class must go via AstMemberSel
|
||||
return "this";
|
||||
return VSelfPointerText{VSelfPointerText::This()};
|
||||
} else if (relativeRefOk && scopep == m_scopep) {
|
||||
return "this";
|
||||
return VSelfPointerText{VSelfPointerText::This()};
|
||||
} else if (relativeRefOk && !m_modSingleton && scopep->aboveScopep() == m_scopep
|
||||
&& VN_IS(scopep->modp(), Module)) {
|
||||
// Reference to scope of instance directly under this module, can just "this->cell",
|
||||
// which can potentially be V3Combined, but note this requires one extra pointer
|
||||
// dereference which is slower, so we only use it if the source scope is not a
|
||||
// singleton.
|
||||
string name = scopep->name();
|
||||
string::size_type pos;
|
||||
if ((pos = name.rfind('.')) != string::npos) name.erase(0, pos + 1);
|
||||
return "this->" + name;
|
||||
return scopeThis(scopep);
|
||||
} else {
|
||||
// Reference to something elsewhere, or relative references are disabled. Use global
|
||||
// variable
|
||||
return "(&" + scopep->nameVlSym() + ")";
|
||||
return scopeVlSyms(scopep);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,11 +185,10 @@ private:
|
|||
if (moreOfSame) {
|
||||
AstIf* const ifp = new AstIf{
|
||||
funcp->fileline(),
|
||||
new AstEq{
|
||||
funcp->fileline(), new AstCExpr{funcp->fileline(), "this", 64},
|
||||
new AstCExpr{funcp->fileline(),
|
||||
string{"&("} + funcp->scopep()->nameVlSym() + ")",
|
||||
64}},
|
||||
new AstEq{funcp->fileline(),
|
||||
new AstCExpr{funcp->fileline(), "this", 64},
|
||||
new AstCExpr{funcp->fileline(),
|
||||
scopeVlSyms(funcp->scopep()).asString(), 64}},
|
||||
returnp};
|
||||
newfuncp->addStmtsp(ifp);
|
||||
} else {
|
||||
|
|
@ -225,15 +246,15 @@ private:
|
|||
const AstScope* const scopep = nodep->varScopep()->scopep();
|
||||
if (varp->isFuncLocal()) {
|
||||
// Reference to function locals need no self pointer
|
||||
nodep->selfPointer("");
|
||||
nodep->selfPointer(VSelfPointerText{VSelfPointerText::Empty()});
|
||||
} else if (scopep->modp() == v3Global.rootp()->constPoolp()->modp()) {
|
||||
// Reference to constant pool value need no self pointer
|
||||
nodep->selfPointer("");
|
||||
nodep->selfPointer(VSelfPointerText{VSelfPointerText::Empty()});
|
||||
} else {
|
||||
nodep->selfPointer(descopedSelfPointer(scopep));
|
||||
}
|
||||
nodep->varScopep(nullptr);
|
||||
UINFO(9, " refout " << nodep << " selfPtr=" << nodep->selfPointer() << endl);
|
||||
UINFO(9, " refout " << nodep << " selfPtr=" << nodep->selfPointer().asString() << endl);
|
||||
}
|
||||
void visit(AstCCall* nodep) override {
|
||||
// UINFO(9, " " << nodep << endl);
|
||||
|
|
|
|||
|
|
@ -503,11 +503,11 @@ public:
|
|||
inline bool inlined() const;
|
||||
|
||||
// Methods that allow DfgVertex to participate in error reporting/messaging
|
||||
void v3errorEnd(std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
void v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
|
||||
m_filelinep->v3errorEnd(str);
|
||||
}
|
||||
void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN
|
||||
VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
VL_RELEASE(V3Error::s().m_mutex) {
|
||||
m_filelinep->v3errorEndFatal(str);
|
||||
}
|
||||
string warnContextPrimary() const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
|
|
|
|||
|
|
@ -117,11 +117,12 @@ public:
|
|||
static void forModCUse(const AstNodeModule* modp, VUseType useType, F action) {
|
||||
for (AstNode* itemp = modp->stmtsp(); itemp; itemp = itemp->nextp()) {
|
||||
if (AstCUse* const usep = VN_CAST(itemp, CUse)) {
|
||||
if (usep->useType() == useType) {
|
||||
if (usep->useType().isInclude()) {
|
||||
if (usep->useType().containsAny(useType)) {
|
||||
if (usep->useType().containsAny(VUseType::INT_INCLUDE)) {
|
||||
action("#include \"" + prefixNameProtect(usep) + ".h\"\n");
|
||||
continue; // Forward declaration is not necessary
|
||||
}
|
||||
if (usep->useType().isFwdClass()) {
|
||||
if (usep->useType().containsAny(VUseType::INT_FWD_CLASS)) {
|
||||
action("class " + prefixNameProtect(usep) + ";\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ void EmitCFunc::emitOpName(AstNode* nodep, const string& format, AstNode* lhsp,
|
|||
UASSERT_OBJ(m_wideTempRefp, nodep,
|
||||
"Wide Op w/ no temp, perhaps missing op in V3EmitC?");
|
||||
COMMA;
|
||||
if (!m_wideTempRefp->selfPointer().empty()) {
|
||||
if (!m_wideTempRefp->selfPointer().isEmpty()) {
|
||||
emitDereference(m_wideTempRefp->selfPointerProtect(m_useSelfForThis));
|
||||
}
|
||||
puts(m_wideTempRefp->varp()->nameProtect());
|
||||
|
|
@ -314,6 +314,7 @@ void EmitCFunc::displayNode(AstNode* nodep, AstScopeName* scopenamep, const stri
|
|||
|
||||
// Convert Verilog display to C printf formats
|
||||
// "%0t" becomes "%d"
|
||||
VL_RESTORER(m_emitDispState);
|
||||
m_emitDispState.clear();
|
||||
string vfmt;
|
||||
string::const_iterator pos = vformat.begin();
|
||||
|
|
@ -516,7 +517,7 @@ void EmitCFunc::emitConstant(AstConst* nodep, AstVarRef* assigntop, const string
|
|||
if (!assigntop) {
|
||||
puts(assignString);
|
||||
} else {
|
||||
if (!assigntop->selfPointer().empty()) {
|
||||
if (!assigntop->selfPointer().isEmpty()) {
|
||||
emitDereference(assigntop->selfPointerProtect(m_useSelfForThis));
|
||||
}
|
||||
puts(assigntop->varp()->nameProtect());
|
||||
|
|
@ -538,7 +539,7 @@ void EmitCFunc::emitConstant(AstConst* nodep, AstVarRef* assigntop, const string
|
|||
if (!assigntop) {
|
||||
puts(assignString);
|
||||
} else {
|
||||
if (!assigntop->selfPointer().empty()) {
|
||||
if (!assigntop->selfPointer().isEmpty()) {
|
||||
emitDereference(assigntop->selfPointerProtect(m_useSelfForThis));
|
||||
}
|
||||
puts(assigntop->varp()->nameProtect());
|
||||
|
|
|
|||
|
|
@ -192,12 +192,10 @@ public:
|
|||
}
|
||||
void emitScIQW(AstVar* nodep) {
|
||||
UASSERT_OBJ(nodep->isSc(), nodep, "emitting SystemC operator on non-SC variable");
|
||||
// clang-format off
|
||||
puts(nodep->isScBigUint() ? "SB"
|
||||
: nodep->isScUint() ? "SU"
|
||||
: nodep->isScBv() ? "SW"
|
||||
: (nodep->isScQuad() ? "SQ" : "SI"));
|
||||
// clang-format on
|
||||
: (nodep->isScQuad() ? "SQ" : "SI"));
|
||||
}
|
||||
void emitDatap(AstNode* nodep) {
|
||||
// When passing to a function with va_args the compiler doesn't
|
||||
|
|
@ -347,6 +345,19 @@ public:
|
|||
emitVarDecl(nodep);
|
||||
}
|
||||
|
||||
void visit(AstCvtDynArrayToPacked* nodep) override {
|
||||
puts("VL_DYN_TO_");
|
||||
emitIQW(nodep);
|
||||
puts("<");
|
||||
const AstNodeDType* const elemDTypep = nodep->fromp()->dtypep()->subDTypep();
|
||||
putbs(elemDTypep->cType("", false, false));
|
||||
puts(">(");
|
||||
iterateAndNextConstNull(nodep->fromp());
|
||||
puts(", ");
|
||||
puts(cvtToStr(elemDTypep->widthMin()));
|
||||
puts(")");
|
||||
}
|
||||
|
||||
void visit(AstNodeAssign* nodep) override {
|
||||
bool paren = true;
|
||||
bool decind = false;
|
||||
|
|
@ -401,6 +412,19 @@ public:
|
|||
puts(cvtToStr(nodep->widthMin()) + ",");
|
||||
iterateAndNextConstNull(nodep->lhsp());
|
||||
puts(", ");
|
||||
} else if (const AstCvtPackedToDynArray* const castp
|
||||
= VN_CAST(nodep->rhsp(), CvtPackedToDynArray)) {
|
||||
puts("VL_ASSIGN_DYN_Q<");
|
||||
putbs(castp->dtypep()->subDTypep()->cType("", false, false));
|
||||
puts(">(");
|
||||
iterateAndNextConstNull(nodep->lhsp());
|
||||
puts(", ");
|
||||
puts(cvtToStr(castp->dtypep()->subDTypep()->widthMin()));
|
||||
puts(", ");
|
||||
puts(cvtToStr(castp->fromp()->widthMin()));
|
||||
puts(", ");
|
||||
rhs = false;
|
||||
iterateAndNextConstNull(castp->fromp());
|
||||
} else if (nodep->isWide() && VN_IS(nodep->lhsp(), VarRef) //
|
||||
&& !VN_IS(nodep->rhsp(), CExpr) //
|
||||
&& !VN_IS(nodep->rhsp(), CMethodHard) //
|
||||
|
|
@ -408,7 +432,8 @@ public:
|
|||
&& !VN_IS(nodep->rhsp(), AssocSel) //
|
||||
&& !VN_IS(nodep->rhsp(), MemberSel) //
|
||||
&& !VN_IS(nodep->rhsp(), StructSel) //
|
||||
&& !VN_IS(nodep->rhsp(), ArraySel)) {
|
||||
&& !VN_IS(nodep->rhsp(), ArraySel) //
|
||||
&& !VN_IS(nodep->rhsp(), ExprStmt)) {
|
||||
// Wide functions assign into the array directly, don't need separate assign statement
|
||||
m_wideTempRefp = VN_AS(nodep->lhsp(), VarRef);
|
||||
paren = false;
|
||||
|
|
@ -469,7 +494,7 @@ public:
|
|||
puts(funcNameProtect(funcp));
|
||||
} else {
|
||||
// Calling regular method/function
|
||||
if (!nodep->selfPointer().empty()) {
|
||||
if (!nodep->selfPointer().isEmpty()) {
|
||||
emitDereference(nodep->selfPointerProtect(m_useSelfForThis));
|
||||
}
|
||||
puts(funcp->nameProtect());
|
||||
|
|
@ -883,6 +908,11 @@ public:
|
|||
iterateAndNextConstNull(nodep->endStmtsp());
|
||||
puts("}\n");
|
||||
}
|
||||
void visit(AstCLocalScope* nodep) override {
|
||||
puts("{\n");
|
||||
iterateAndNextConstNull(nodep->stmtsp());
|
||||
puts("}\n");
|
||||
}
|
||||
void visit(AstJumpGo* nodep) override {
|
||||
puts("goto __Vlabel" + cvtToStr(nodep->labelp()->blockp()->labelNum()) + ";\n");
|
||||
}
|
||||
|
|
@ -1235,7 +1265,7 @@ public:
|
|||
} else if (varp->isIfaceRef()) {
|
||||
puts(nodep->selfPointerProtect(m_useSelfForThis));
|
||||
return;
|
||||
} else if (!nodep->selfPointer().empty()) {
|
||||
} else if (!nodep->selfPointer().isEmpty()) {
|
||||
emitDereference(nodep->selfPointerProtect(m_useSelfForThis));
|
||||
}
|
||||
puts(nodep->varp()->nameProtect());
|
||||
|
|
|
|||
|
|
@ -380,11 +380,10 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
std::set<string> cuse_set;
|
||||
auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); };
|
||||
|
||||
forModCUse(modp, VUseType::INT_INCLUDE, add_to_cuse_set);
|
||||
forModCUse(modp, VUseType::INT_FWD_CLASS, add_to_cuse_set);
|
||||
forModCUse(modp, VUseType::INT_FWD_CLASS | VUseType::INT_INCLUDE, add_to_cuse_set);
|
||||
if (const AstClassPackage* const packagep = VN_CAST(modp, ClassPackage)) {
|
||||
forModCUse(packagep->classp(), VUseType::INT_INCLUDE, add_to_cuse_set);
|
||||
forModCUse(packagep->classp(), VUseType::INT_FWD_CLASS, add_to_cuse_set);
|
||||
forModCUse(packagep->classp(), VUseType::INT_INCLUDE | VUseType::INT_FWD_CLASS,
|
||||
add_to_cuse_set);
|
||||
}
|
||||
|
||||
for (const string& s : cuse_set) puts(s);
|
||||
|
|
|
|||
|
|
@ -59,18 +59,18 @@ class EmitCGatherDependencies final : VNVisitorConst {
|
|||
}
|
||||
}
|
||||
}
|
||||
void addSelfDependency(const string& selfPointer, AstNode* nodep) {
|
||||
if (selfPointer.empty()) {
|
||||
void addSelfDependency(VSelfPointerText selfPointer, AstNode* nodep) {
|
||||
if (selfPointer.isEmpty()) {
|
||||
// No self pointer (e.g.: function locals, const pool values, loose static methods),
|
||||
// so no dependency
|
||||
} else if (VString::startsWith(selfPointer, "this")) {
|
||||
} else if (selfPointer.hasThis()) {
|
||||
// Dereferencing 'this', we need the definition of this module, which is also the
|
||||
// module that contains the variable.
|
||||
addModDependency(EmitCParentModule::get(nodep));
|
||||
} else {
|
||||
// Must be an absolute reference
|
||||
UASSERT_OBJ(selfPointer.find("vlSymsp") != string::npos, nodep,
|
||||
"Unknown self pointer: '" << selfPointer << "'");
|
||||
UASSERT_OBJ(selfPointer.isVlSym(), nodep,
|
||||
"Unknown self pointer: '" << selfPointer.asString() << "'");
|
||||
// Dereferencing vlSymsp, so we need it's definition...
|
||||
addSymsDependency();
|
||||
}
|
||||
|
|
@ -677,7 +677,7 @@ class EmitCTrace final : EmitCFunc {
|
|||
string fstvt;
|
||||
// Doubles have special decoding properties, so must indicate if a double
|
||||
if (nodep->dtypep()->basicp()->isDouble()) {
|
||||
if (vartype == VVarType::GPARAM || vartype == VVarType::LPARAM) {
|
||||
if (vartype.isParam()) {
|
||||
fstvt = "FST_VT_VCD_REAL_PARAMETER";
|
||||
} else {
|
||||
fstvt = "FST_VT_VCD_REAL";
|
||||
|
|
|
|||
|
|
@ -234,6 +234,12 @@ class EmitCModel final : public EmitCFunc {
|
|||
puts("const char* hierName() const override final;\n");
|
||||
puts("const char* modelName() const override final;\n");
|
||||
puts("unsigned threads() const override final;\n");
|
||||
puts("/// Prepare for cloning the model at the process level (e.g. fork in Linux)\n");
|
||||
puts("/// Release necessary resources. Called before cloning.\n");
|
||||
puts("void prepareClone() const;\n");
|
||||
puts("/// Re-init after cloning the model at the process level (e.g. fork in Linux)\n");
|
||||
puts("/// Re-allocate necessary resources. Called after cloning.\n");
|
||||
puts("void atClone() const;\n");
|
||||
if (v3Global.opt.trace()) {
|
||||
puts("std::unique_ptr<VerilatedTraceConfig> traceConfig() const override final;\n");
|
||||
}
|
||||
|
|
@ -479,6 +485,15 @@ class EmitCModel final : public EmitCFunc {
|
|||
+ "\"; }\n");
|
||||
puts("unsigned " + topClassName() + "::threads() const { return "
|
||||
+ cvtToStr(std::max(1, v3Global.opt.threads())) + "; }\n");
|
||||
puts("void " + topClassName()
|
||||
+ "::prepareClone() const { contextp()->prepareClone(); }\n");
|
||||
puts("void " + topClassName() + "::atClone() const {\n");
|
||||
if (v3Global.opt.threads() > 1) {
|
||||
puts("vlSymsp->__Vm_threadPoolp = static_cast<VlThreadPool*>(");
|
||||
}
|
||||
puts("contextp()->threadPoolpOnClone()");
|
||||
if (v3Global.opt.threads() > 1) puts(")");
|
||||
puts(";\n}\n");
|
||||
|
||||
if (v3Global.opt.trace()) {
|
||||
puts("std::unique_ptr<VerilatedTraceConfig> " + topClassName()
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ void EmitCSyms::emitSymHdr() {
|
|||
|
||||
if (v3Global.opt.mtasks()) {
|
||||
puts("\n// MULTI-THREADING\n");
|
||||
puts("VlThreadPool* const __Vm_threadPoolp;\n");
|
||||
puts("VlThreadPool* __Vm_threadPoolp;\n");
|
||||
puts("bool __Vm_even_cycle__ico = false;\n");
|
||||
puts("bool __Vm_even_cycle__act = false;\n");
|
||||
puts("bool __Vm_even_cycle__nba = false;\n");
|
||||
|
|
|
|||
|
|
@ -664,6 +664,12 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public EmitCBaseVisitorConst {
|
|||
iterateAndNextConstNull(nodep->pinsp());
|
||||
puts(")");
|
||||
}
|
||||
void visit(AstCCall* nodep) override {
|
||||
puts(nodep->funcp()->name());
|
||||
puts("(");
|
||||
iterateAndNextConstNull(nodep->argsp());
|
||||
puts(")");
|
||||
}
|
||||
void visit(AstArg* nodep) override { iterateAndNextConstNull(nodep->exprp()); }
|
||||
void visit(AstPrintTimeScale* nodep) override {
|
||||
puts(nodep->verilogKwd());
|
||||
|
|
@ -676,10 +682,11 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public EmitCBaseVisitorConst {
|
|||
putfs(nodep, nodep->varScopep()->prettyName());
|
||||
} else {
|
||||
if (nodep->varp()) {
|
||||
if (nodep->selfPointer().empty()) {
|
||||
if (nodep->selfPointer().isEmpty()) {
|
||||
putfs(nodep, nodep->varp()->prettyName());
|
||||
} else {
|
||||
putfs(nodep, nodep->selfPointer() + "->");
|
||||
putfs(nodep, nodep->selfPointer().asString());
|
||||
putfs(nodep, "->");
|
||||
puts(nodep->varp()->prettyName());
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -291,3 +291,35 @@ void V3Error::vlAbort() {
|
|||
VL_GCOV_DUMP();
|
||||
std::abort();
|
||||
}
|
||||
void V3Error::v3errorAcquireLock() VL_ACQUIRE(s().m_mutex) {
|
||||
#ifndef V3ERROR_NO_GLOBAL_
|
||||
V3Error::s().m_mutex.lockCheckStopRequest(
|
||||
[]() -> void { V3ThreadPool::s().waitIfStopRequested(); });
|
||||
#else
|
||||
V3Error::s().m_mutex.lock();
|
||||
#endif
|
||||
}
|
||||
std::ostringstream& V3Error::v3errorPrep(V3ErrorCode code) VL_ACQUIRE(s().m_mutex) {
|
||||
v3errorAcquireLock();
|
||||
s().v3errorPrep(code);
|
||||
return v3errorStr();
|
||||
}
|
||||
std::ostringstream& V3Error::v3errorPrepFileLine(V3ErrorCode code, const char* file, int line)
|
||||
VL_ACQUIRE(s().m_mutex) {
|
||||
v3errorPrep(code) << file << ":" << std::dec << line << ": ";
|
||||
return v3errorStr();
|
||||
}
|
||||
std::ostringstream& V3Error::v3errorStr() VL_REQUIRES(s().m_mutex) { return s().v3errorStr(); }
|
||||
void V3Error::v3errorEnd(std::ostringstream& sstr, const string& extra) VL_RELEASE(s().m_mutex) {
|
||||
s().v3errorEnd(sstr, extra);
|
||||
V3Error::s().m_mutex.unlock();
|
||||
}
|
||||
|
||||
void v3errorEnd(std::ostringstream& sstr) VL_RELEASE(V3Error::s().m_mutex) {
|
||||
V3Error::v3errorEnd(sstr);
|
||||
}
|
||||
void v3errorEndFatal(std::ostringstream& sstr) VL_RELEASE(V3Error::s().m_mutex) {
|
||||
V3Error::v3errorEnd(sstr);
|
||||
assert(0); // LCOV_EXCL_LINE
|
||||
VL_UNREACHABLE;
|
||||
}
|
||||
|
|
|
|||
103
src/V3Error.h
103
src/V3Error.h
|
|
@ -286,9 +286,13 @@ inline std::ostream& operator<<(std::ostream& os, const V3ErrorCode& rhs) {
|
|||
}
|
||||
|
||||
// ######################################################################
|
||||
class V3Error;
|
||||
|
||||
class V3ErrorGuarded final {
|
||||
// Should only be used by V3ErrorGuarded::m_mutex is already locked
|
||||
// contains guarded members
|
||||
friend class V3Error;
|
||||
|
||||
public:
|
||||
using MessagesSet = std::set<std::string>;
|
||||
using ErrorExitCb = void (*)(void);
|
||||
|
|
@ -319,6 +323,16 @@ private:
|
|||
= MAX_ERRORS; // Option: --error-limit Number of errors before exit
|
||||
bool m_warnFatal VL_GUARDED_BY(m_mutex) = true; // Option: --warnFatal Warnings are fatal
|
||||
std::ostringstream m_errorStr VL_GUARDED_BY(m_mutex); // Error string being formed
|
||||
|
||||
void v3errorPrep(V3ErrorCode code) VL_REQUIRES(m_mutex) {
|
||||
m_errorStr.str("");
|
||||
m_errorCode = code;
|
||||
m_errorContexted = false;
|
||||
m_errorSuppressed = false;
|
||||
}
|
||||
std::ostringstream& v3errorStr() VL_REQUIRES(m_mutex) { return m_errorStr; }
|
||||
void v3errorEnd(std::ostringstream& sstr, const string& extra = "") VL_REQUIRES(m_mutex);
|
||||
|
||||
public:
|
||||
V3RecursiveMutex m_mutex; // Make sure only single thread is in class
|
||||
|
||||
|
|
@ -361,13 +375,6 @@ public:
|
|||
int errorLimit() VL_REQUIRES(m_mutex) { return m_errorLimit; }
|
||||
void warnFatal(bool flag) VL_REQUIRES(m_mutex) { m_warnFatal = flag; }
|
||||
bool warnFatal() VL_REQUIRES(m_mutex) { return m_warnFatal; }
|
||||
void v3errorPrep(V3ErrorCode code) VL_REQUIRES(m_mutex) {
|
||||
m_errorStr.str("");
|
||||
m_errorCode = code;
|
||||
m_errorContexted = false;
|
||||
m_errorSuppressed = false;
|
||||
}
|
||||
std::ostringstream& v3errorStr() VL_REQUIRES(m_mutex) { return m_errorStr; }
|
||||
V3ErrorCode errorCode() VL_REQUIRES(m_mutex) { return m_errorCode; }
|
||||
bool errorContexted() VL_REQUIRES(m_mutex) { return m_errorContexted; }
|
||||
int warnCount() VL_REQUIRES(m_mutex) { return m_warnCount; }
|
||||
|
|
@ -385,7 +392,6 @@ public:
|
|||
void describedWarnings(bool flag) VL_REQUIRES(m_mutex) { m_describedWarnings = flag; }
|
||||
int tellManual() VL_REQUIRES(m_mutex) { return m_tellManual; }
|
||||
void tellManual(int level) VL_REQUIRES(m_mutex) { m_tellManual = level; }
|
||||
void v3errorEnd(std::ostringstream& sstr, const string& extra = "") VL_REQUIRES(m_mutex);
|
||||
void suppressThisWarning() VL_REQUIRES(m_mutex);
|
||||
string warnContextNone() VL_REQUIRES(m_mutex) {
|
||||
errorContexted(true);
|
||||
|
|
@ -512,66 +518,32 @@ public:
|
|||
|
||||
// Internals for v3error()/v3fatal() macros only
|
||||
// Error end takes the string stream to output, be careful to seek() as needed
|
||||
static void v3errorPrep(V3ErrorCode code) VL_MT_SAFE_EXCLUDES(s().m_mutex) {
|
||||
const V3RecursiveLockGuard guard{s().m_mutex};
|
||||
s().v3errorPrep(code);
|
||||
}
|
||||
static std::ostringstream& v3errorStr() VL_MT_SAFE_EXCLUDES(s().m_mutex) {
|
||||
const V3RecursiveLockGuard guard{s().m_mutex};
|
||||
return s().v3errorStr();
|
||||
}
|
||||
static void vlAbort();
|
||||
static void v3errorAcquireLock() VL_ACQUIRE(s().m_mutex);
|
||||
static std::ostringstream& v3errorPrep(V3ErrorCode code) VL_ACQUIRE(s().m_mutex);
|
||||
static std::ostringstream& v3errorPrepFileLine(V3ErrorCode code, const char* file, int line)
|
||||
VL_ACQUIRE(s().m_mutex);
|
||||
static std::ostringstream& v3errorStr() VL_REQUIRES(s().m_mutex);
|
||||
// static, but often overridden in classes.
|
||||
static void v3errorEnd(std::ostringstream& sstr, const string& extra = "")
|
||||
VL_MT_SAFE_EXCLUDES(s().m_mutex) VL_MT_SAFE {
|
||||
const V3RecursiveLockGuard guard{s().m_mutex};
|
||||
s().v3errorEnd(sstr, extra);
|
||||
}
|
||||
// We can't call 's().v3errorEnd' directly in 'v3ErrorEnd'/'v3errorEndFatal',
|
||||
// due to bug in GCC (tested on 11.3.0 version with --enable-m32)
|
||||
// causing internal error when backtrace is printed.
|
||||
// Instead use this wrapper.
|
||||
static void v3errorEndGuardedCall(std::ostringstream& sstr, const string& extra = "")
|
||||
VL_REQUIRES(s().m_mutex) VL_MT_SAFE {
|
||||
s().v3errorEnd(sstr, extra);
|
||||
}
|
||||
VL_RELEASE(s().m_mutex);
|
||||
static void vlAbort();
|
||||
};
|
||||
|
||||
// Global versions, so that if the class doesn't define a operator, we get the functions anyways.
|
||||
inline void v3errorEnd(std::ostringstream& sstr) VL_REQUIRES(V3Error::s().m_mutex) VL_MT_SAFE {
|
||||
V3Error::v3errorEndGuardedCall(sstr);
|
||||
}
|
||||
inline void v3errorEndFatal(std::ostringstream& sstr)
|
||||
VL_REQUIRES(V3Error::s().m_mutex) VL_MT_SAFE {
|
||||
V3Error::v3errorEndGuardedCall(sstr);
|
||||
assert(0); // LCOV_EXCL_LINE
|
||||
VL_UNREACHABLE;
|
||||
}
|
||||
|
||||
#ifndef V3ERROR_NO_GLOBAL_
|
||||
#define V3ErrorLockAndCheckStopRequested \
|
||||
V3Error::s().m_mutex.lockCheckStopRequest( \
|
||||
[]() -> void { V3ThreadPool::s().waitIfStopRequested(); })
|
||||
#else
|
||||
#define V3ErrorLockAndCheckStopRequested V3Error::s().m_mutex.lock()
|
||||
#endif
|
||||
// Global versions, so that if the class doesn't define an operator, we get the functions anyway.
|
||||
void v3errorEnd(std::ostringstream& sstr) VL_RELEASE(V3Error::s().m_mutex);
|
||||
void v3errorEndFatal(std::ostringstream& sstr) VL_RELEASE(V3Error::s().m_mutex) VL_ATTR_NORETURN;
|
||||
|
||||
// Theses allow errors using << operators: v3error("foo"<<"bar");
|
||||
// Careful, you can't put () around msg, as you would in most macro definitions
|
||||
// Note the commas are the comma operator, not separating arguments. These are needed to ensure
|
||||
// evaluation order as otherwise we couldn't ensure v3errorPrep is called first.
|
||||
// Note: due to limitations of clang thread-safety analysis, we can't use
|
||||
// lock guard here, instead we are locking the mutex as first operation in temporary,
|
||||
// but we are unlocking the mutex after function using comma operator.
|
||||
// This way macros should also work when they are in 'if' stmt without '{}'.
|
||||
#define v3warnCode(code, msg) \
|
||||
v3errorEnd((V3ErrorLockAndCheckStopRequested, V3Error::s().v3errorPrep(code), \
|
||||
(V3Error::s().v3errorStr() << msg), V3Error::s().v3errorStr())), \
|
||||
V3Error::s().m_mutex.unlock()
|
||||
// Careful, you can't put () around msg, as you would in most macro definitions.
|
||||
// 'V3Error::v3errorPrep(code) << msg' could be more efficient but the order of function calls in a
|
||||
// single statement can be arbitrary until C++17, thus make it possible to execute
|
||||
// V3Error::v3errorPrep that acquires the lock after functions in the msg may require it. So we use
|
||||
// the comma operator (,) to guarantee the execution order here.
|
||||
#define v3errorBuildMessage(prep, msg) \
|
||||
(prep, static_cast<std::ostringstream&>(V3Error::v3errorStr() << msg))
|
||||
#define v3warnCode(code, msg) v3errorEnd(v3errorBuildMessage(V3Error::v3errorPrep(code), msg))
|
||||
#define v3warnCodeFatal(code, msg) \
|
||||
v3errorEndFatal((V3ErrorLockAndCheckStopRequested, V3Error::s().v3errorPrep(code), \
|
||||
(V3Error::s().v3errorStr() << msg), V3Error::s().v3errorStr())), \
|
||||
V3Error::s().m_mutex.unlock()
|
||||
v3errorEndFatal(v3errorBuildMessage(V3Error::v3errorPrep(code), msg))
|
||||
#define v3warn(code, msg) v3warnCode(V3ErrorCode::code, msg)
|
||||
#define v3info(msg) v3warnCode(V3ErrorCode::EC_INFO, msg)
|
||||
#define v3error(msg) v3warnCode(V3ErrorCode::EC_ERROR, msg)
|
||||
|
|
@ -580,14 +552,11 @@ inline void v3errorEndFatal(std::ostringstream& sstr)
|
|||
#define v3fatalExit(msg) v3warnCodeFatal(V3ErrorCode::EC_FATALEXIT, msg)
|
||||
// Use this instead of fatal() to mention the source code line.
|
||||
#define v3fatalSrc(msg) \
|
||||
v3warnCodeFatal(V3ErrorCode::EC_FATALSRC, \
|
||||
__FILE__ << ":" << std::dec << __LINE__ << ": " << msg)
|
||||
v3errorEndFatal(v3errorBuildMessage( \
|
||||
V3Error::v3errorPrepFileLine(V3ErrorCode::EC_FATALSRC, __FILE__, __LINE__), msg))
|
||||
// Use this when normal v3fatal is called in static method that overrides fileline.
|
||||
#define v3fatalStatic(msg) \
|
||||
(::v3errorEndFatal((V3ErrorLockAndCheckStopRequested, \
|
||||
V3Error::s().v3errorPrep(V3ErrorCode::EC_FATAL), \
|
||||
(V3Error::s().v3errorStr() << msg), V3Error::s().v3errorStr()))), \
|
||||
V3Error::s().m_mutex.unlock()
|
||||
::v3errorEndFatal(v3errorBuildMessage(V3Error::v3errorPrep(V3ErrorCode::EC_FATAL), msg))
|
||||
|
||||
#define UINFO(level, stmsg) \
|
||||
do { \
|
||||
|
|
|
|||
|
|
@ -202,7 +202,9 @@ private:
|
|||
new AstShiftR{fl, lhip,
|
||||
new AstConst{fl, static_cast<uint32_t>(nbitsonright)},
|
||||
VL_EDATASIZE}},
|
||||
new AstAnd{fl, new AstConst{fl, AstConst::SizedEData{}, ~VL_MASK_E(loffset)},
|
||||
new AstAnd{fl,
|
||||
new AstConst{fl, AstConst::SizedEData{},
|
||||
static_cast<uint32_t>(~VL_MASK_E(loffset))},
|
||||
new AstShiftL{fl, llowp,
|
||||
new AstConst{fl, static_cast<uint32_t>(loffset)},
|
||||
VL_EDATASIZE}}};
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
// clang-format off
|
||||
#include "V3Error.h"
|
||||
#include "V3FileLine.h"
|
||||
#include "V3Os.h"
|
||||
#include "V3String.h"
|
||||
#ifndef V3ERROR_NO_GLOBAL_
|
||||
# include "V3Global.h"
|
||||
|
|
@ -286,12 +287,7 @@ FileLine* FileLine::copyOrSameFileLine() {
|
|||
return newp;
|
||||
}
|
||||
|
||||
string FileLine::filebasename() const VL_MT_SAFE {
|
||||
string name = filename();
|
||||
string::size_type pos;
|
||||
if ((pos = name.rfind('/')) != string::npos) name.erase(0, pos + 1);
|
||||
return name;
|
||||
}
|
||||
string FileLine::filebasename() const VL_MT_SAFE { return V3Os::filenameNonDir(filename()); }
|
||||
|
||||
string FileLine::filebasenameNoExt() const {
|
||||
string name = filebasename();
|
||||
|
|
@ -383,7 +379,7 @@ bool FileLine::warnIsOff(V3ErrorCode code) const {
|
|||
|
||||
// cppverilator-suppress constParameter
|
||||
void FileLine::v3errorEnd(std::ostringstream& sstr, const string& extra)
|
||||
VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
VL_RELEASE(V3Error::s().m_mutex) {
|
||||
std::ostringstream nsstr;
|
||||
if (lastLineno()) nsstr << this;
|
||||
nsstr << sstr.str();
|
||||
|
|
@ -400,7 +396,7 @@ void FileLine::v3errorEnd(std::ostringstream& sstr, const string& extra)
|
|||
nsstr << warnContextPrimary();
|
||||
}
|
||||
if (!m_waive) V3Waiver::addEntry(V3Error::s().errorCode(), filename(), sstr.str());
|
||||
V3Error::s().v3errorEnd(nsstr, lstr.str());
|
||||
V3Error::v3errorEnd(nsstr, lstr.str());
|
||||
}
|
||||
|
||||
string FileLine::warnMore() const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
|
|
|
|||
|
|
@ -318,9 +318,9 @@ public:
|
|||
|
||||
// OPERATORS
|
||||
void v3errorEnd(std::ostringstream& str, const string& extra = "")
|
||||
VL_REQUIRES(V3Error::s().m_mutex);
|
||||
VL_RELEASE(V3Error::s().m_mutex);
|
||||
void v3errorEndFatal(std::ostringstream& str) VL_ATTR_NORETURN
|
||||
VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
VL_RELEASE(V3Error::s().m_mutex) {
|
||||
v3errorEnd(str);
|
||||
assert(0); // LCOV_EXCL_LINE
|
||||
VL_UNREACHABLE;
|
||||
|
|
|
|||
458
src/V3Fork.cpp
458
src/V3Fork.cpp
|
|
@ -18,11 +18,23 @@
|
|||
//
|
||||
// Each module:
|
||||
// Look for FORKs [JOIN_NONE]/[JOIN_ANY]
|
||||
// VARREF(var) -> MEMBERSEL(var->name, VARREF(dynscope)) (for write/RW refs)
|
||||
// FORK(stmts) -> TASK(stmts), FORK(TASKREF(inits))
|
||||
//
|
||||
// FORKs that spawn tasks which might outlive their parents require those
|
||||
// tasks to carry their own frames and as such they require their own
|
||||
// variable scopes.
|
||||
// There are two mechanisms that work together to achieve that. ForkVisitor
|
||||
// moves bodies of forked prcesses into new tasks, which results in them getting their
|
||||
// own scopes. The original statements get replaced with a call to the task which
|
||||
// passes the required variables by value.
|
||||
// The second mechanism, DynScopeVisitor, is designed to handle variables which can't be
|
||||
// captured by value and instead require a reference. Those variables get moved into an
|
||||
// "anonymous" object, ie. a class with appropriate fields gets generated and an object
|
||||
// of this class gets instantiated in place of the original variable declarations.
|
||||
// Any references to those variables are replaced with references to the object's field.
|
||||
// Since objects are reference-counted this ensures that the variables are accessible
|
||||
// as long as both the parent and the forked processes require them to be.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
|
|
@ -33,13 +45,411 @@
|
|||
|
||||
#include "V3Ast.h"
|
||||
#include "V3AstNodeExpr.h"
|
||||
#include "V3Error.h"
|
||||
#include "V3Global.h"
|
||||
#include "V3MemberMap.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
class ForkDynScopeInstance final {
|
||||
public:
|
||||
AstClass* m_classp = nullptr; // Class for holding variables of dynamic scope
|
||||
AstClassRefDType* m_refDTypep = nullptr; // RefDType for the above
|
||||
AstVar* m_handlep = nullptr; // Class handle for holding variables of dynamic scope
|
||||
|
||||
// True if the instance exists
|
||||
bool initialized() const { return m_classp != nullptr; }
|
||||
};
|
||||
|
||||
class ForkDynScopeFrame final {
|
||||
private:
|
||||
// MEMBERS
|
||||
AstNodeModule* const m_modp; // Module to insert the scope into
|
||||
AstNode* const m_procp; // Procedure/block associated with that dynscope
|
||||
std::set<AstVar*> m_captures; // Variables to be moved into the dynscope
|
||||
ForkDynScopeInstance m_instance; // Nodes to be injected into the AST to create the dynscope
|
||||
|
||||
public:
|
||||
ForkDynScopeFrame(AstNodeModule* modp, AstNode* procp)
|
||||
: m_modp{modp}
|
||||
, m_procp{procp} {}
|
||||
|
||||
ForkDynScopeInstance& createInstancePrototype() {
|
||||
UASSERT_OBJ(!m_instance.initialized(), m_procp, "Dynamic scope already instantiated.");
|
||||
|
||||
m_instance.m_classp
|
||||
= new AstClass{m_procp->fileline(), generateDynScopeClassName(m_procp)};
|
||||
m_instance.m_refDTypep
|
||||
= new AstClassRefDType{m_procp->fileline(), m_instance.m_classp, nullptr};
|
||||
v3Global.rootp()->typeTablep()->addTypesp(m_instance.m_refDTypep);
|
||||
m_instance.m_handlep
|
||||
= new AstVar{m_procp->fileline(), VVarType::BLOCKTEMP,
|
||||
generateDynScopeHandleName(m_procp), m_instance.m_refDTypep};
|
||||
m_instance.m_handlep->funcLocal(true);
|
||||
m_instance.m_handlep->lifetime(VLifetime::AUTOMATIC);
|
||||
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
const ForkDynScopeInstance& instance() const { return m_instance; }
|
||||
void captureVarInsert(AstVar* varp) { m_captures.insert(varp); }
|
||||
bool captured(AstVar* varp) { return m_captures.count(varp) != 0; }
|
||||
AstNode* procp() const { return m_procp; }
|
||||
|
||||
void populateClass() {
|
||||
UASSERT_OBJ(m_instance.initialized(), m_procp, "No DynScope prototype");
|
||||
|
||||
// Move variables into the class
|
||||
for (AstVar* varp : m_captures) {
|
||||
if (varp->direction() == VDirection::INPUT) {
|
||||
varp = varp->cloneTree(false);
|
||||
varp->direction(VDirection::NONE);
|
||||
} else {
|
||||
varp->unlinkFrBack();
|
||||
}
|
||||
varp->funcLocal(false);
|
||||
varp->varType(VVarType::MEMBER);
|
||||
varp->lifetime(VLifetime::AUTOMATIC);
|
||||
varp->usedLoopIdx(false); // No longer unrollable
|
||||
m_instance.m_classp->addStmtsp(varp);
|
||||
}
|
||||
|
||||
// Create class's constructor
|
||||
AstFunc* const newp
|
||||
= new AstFunc{m_instance.m_classp->fileline(), "new", nullptr, nullptr};
|
||||
newp->isConstructor(true);
|
||||
newp->classMethod(true);
|
||||
newp->dtypep(newp->findVoidDType());
|
||||
m_instance.m_classp->addStmtsp(newp);
|
||||
}
|
||||
|
||||
void linkNodes(VMemberMap& memberMap) {
|
||||
UASSERT_OBJ(m_instance.initialized(), m_procp, "No dynamic scope prototype");
|
||||
UASSERT_OBJ(!linked(), m_instance.m_handlep, "Handle already linked");
|
||||
|
||||
if (VN_IS(m_procp, Fork)) {
|
||||
linkNodesOfFork(memberMap);
|
||||
return;
|
||||
}
|
||||
|
||||
AstNode* stmtp = getProcStmts();
|
||||
UASSERT(stmtp, "trying to instantiate dynamic scope while not under proc");
|
||||
VNRelinker stmtpHandle;
|
||||
stmtp->unlinkFrBackWithNext(&stmtpHandle);
|
||||
|
||||
// Find node after last variable declaration
|
||||
AstNode* initp = stmtp;
|
||||
while (initp && VN_IS(initp, Var)) initp = initp->nextp();
|
||||
UASSERT(stmtp, "Procedure lacks body");
|
||||
UASSERT(initp, "Procedure lacks statements besides declarations");
|
||||
|
||||
AstNew* const newp = new AstNew{m_procp->fileline(), nullptr};
|
||||
newp->taskp(VN_AS(memberMap.findMember(m_instance.m_classp, "new"), NodeFTask));
|
||||
newp->dtypep(m_instance.m_refDTypep);
|
||||
newp->classOrPackagep(m_instance.m_classp);
|
||||
|
||||
AstNode* const asgnp = new AstAssign{
|
||||
m_procp->fileline(),
|
||||
new AstVarRef{m_procp->fileline(), m_instance.m_handlep, VAccess::WRITE}, newp};
|
||||
|
||||
AstNode* initsp = nullptr; // Arguments need to be copied
|
||||
for (AstVar* varp : m_captures) {
|
||||
if (varp->direction() != VDirection::INPUT) continue;
|
||||
|
||||
AstMemberSel* const memberselp = new AstMemberSel{
|
||||
varp->fileline(),
|
||||
new AstVarRef{varp->fileline(), m_instance.m_handlep, VAccess::WRITE},
|
||||
varp->dtypep()};
|
||||
memberselp->name(varp->name());
|
||||
memberselp->varp(VN_AS(memberMap.findMember(m_instance.m_classp, varp->name()), Var));
|
||||
AstNode* initAsgnp
|
||||
= new AstAssign{varp->fileline(), memberselp,
|
||||
new AstVarRef{varp->fileline(), varp, VAccess::READ}};
|
||||
initsp = AstNode::addNext(initsp, initAsgnp);
|
||||
}
|
||||
if (initsp) AstNode::addNext(asgnp, initsp);
|
||||
|
||||
if (initp != stmtp) {
|
||||
initp->addHereThisAsNext(asgnp);
|
||||
} else {
|
||||
AstNode::addNext(asgnp, static_cast<AstNode*>(initp));
|
||||
stmtp = asgnp;
|
||||
}
|
||||
|
||||
AstNode::addNext(static_cast<AstNode*>(m_instance.m_handlep), stmtp);
|
||||
stmtpHandle.relink(m_instance.m_handlep);
|
||||
m_modp->addStmtsp(m_instance.m_classp);
|
||||
}
|
||||
|
||||
bool linked() const { return m_instance.initialized() && m_instance.m_handlep->backp(); }
|
||||
|
||||
private:
|
||||
AstAssign* instantiateDynScope(VMemberMap& memberMap) {
|
||||
AstNew* const newp = new AstNew{m_procp->fileline(), nullptr};
|
||||
newp->taskp(VN_AS(memberMap.findMember(m_instance.m_classp, "new"), NodeFTask));
|
||||
newp->dtypep(m_instance.m_refDTypep);
|
||||
newp->classOrPackagep(m_instance.m_classp);
|
||||
|
||||
return new AstAssign{
|
||||
m_procp->fileline(),
|
||||
new AstVarRef{m_procp->fileline(), m_instance.m_handlep, VAccess::WRITE}, newp};
|
||||
}
|
||||
|
||||
void linkNodesOfFork(VMemberMap& memberMap) {
|
||||
// Special case
|
||||
|
||||
AstFork* const forkp = VN_AS(m_procp, Fork);
|
||||
VNRelinker forkHandle;
|
||||
forkp->unlinkFrBack(&forkHandle);
|
||||
|
||||
AstBegin* const beginp = new AstBegin{
|
||||
forkp->fileline(),
|
||||
"_Vwrapped_" + (forkp->name().empty() ? cvtToHex(forkp) : forkp->name()),
|
||||
m_instance.m_handlep, false, true};
|
||||
forkHandle.relink(beginp);
|
||||
|
||||
AstNode* const instAsgnp = instantiateDynScope(memberMap);
|
||||
|
||||
beginp->stmtsp()->addNext(instAsgnp);
|
||||
beginp->stmtsp()->addNext(forkp);
|
||||
|
||||
if (forkp->initsp()) {
|
||||
forkp->initsp()->foreach([forkp](AstAssign* asgnp) {
|
||||
asgnp->unlinkFrBack();
|
||||
forkp->addHereThisAsNext(asgnp);
|
||||
});
|
||||
}
|
||||
UASSERT_OBJ(!forkp->initsp(), forkp, "Leftover nodes in block_item_declaration");
|
||||
|
||||
m_modp->addStmtsp(m_instance.m_classp);
|
||||
}
|
||||
|
||||
static string generateDynScopeClassName(const AstNode* fromp) {
|
||||
string n = "__VDynScope__" + (!fromp->name().empty() ? (fromp->name() + "__") : "ANON__")
|
||||
+ cvtToHex(fromp);
|
||||
return n;
|
||||
}
|
||||
|
||||
static string generateDynScopeHandleName(const AstNode* fromp) {
|
||||
return "__VDynScope_" + (fromp->name().empty() ? cvtToHex(fromp) : fromp->name());
|
||||
}
|
||||
|
||||
AstNode* getProcStmts() {
|
||||
AstNode* stmtsp = nullptr;
|
||||
if (!m_procp) return nullptr;
|
||||
if (AstBegin* beginp = VN_CAST(m_procp, Begin)) {
|
||||
stmtsp = beginp->stmtsp();
|
||||
} else if (AstNodeFTask* taskp = VN_CAST(m_procp, NodeFTask)) {
|
||||
stmtsp = taskp->stmtsp();
|
||||
} else {
|
||||
m_procp->v3fatalSrc("m_procp is not a begin block or a procedure");
|
||||
}
|
||||
return stmtsp;
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Dynamic scope visitor, creates classes and objects for dynamic scoping of variables and
|
||||
// replaces references to varibles that need a dynamic scope with references to object's
|
||||
// members
|
||||
|
||||
class DynScopeVisitor final : public VNVisitor {
|
||||
private:
|
||||
// NODE STATE
|
||||
// AstVar::user1() -> int, timing-control fork nesting level of that variable
|
||||
// AstVarRef::user2() -> bool, 1 = Node is a class handle reference. The handle gets
|
||||
// modified in the context of this reference.
|
||||
const VNUser1InUse m_inuser1;
|
||||
const VNUser2InUse m_inuser2;
|
||||
|
||||
// STATE
|
||||
AstNodeModule* m_modp = nullptr; // Module we are currently under
|
||||
AstNode* m_procp = nullptr; // Function/task/block we are currently under
|
||||
std::map<AstNode*, ForkDynScopeFrame*>
|
||||
m_frames; // Mapping from nodes to related DynScopeFrames
|
||||
VMemberMap m_memberMap; // Class member look-up
|
||||
int m_forkDepth = 0; // Number of asynchronous forks we are currently under
|
||||
bool m_afterTimingControl = false; // A timing control might've be executed in the current
|
||||
// process
|
||||
|
||||
// METHODS
|
||||
|
||||
ForkDynScopeFrame* frameOf(AstNode* nodep) {
|
||||
auto frameIt = m_frames.find(nodep);
|
||||
if (frameIt == m_frames.end()) return nullptr;
|
||||
return frameIt->second;
|
||||
}
|
||||
|
||||
const ForkDynScopeFrame* frameOf(AstNode* nodep) const {
|
||||
auto frameIt = m_frames.find(nodep);
|
||||
if (frameIt == m_frames.end()) return nullptr;
|
||||
return frameIt->second;
|
||||
}
|
||||
|
||||
ForkDynScopeFrame* pushDynScopeFrame(AstNode* procp) {
|
||||
ForkDynScopeFrame* const framep = new ForkDynScopeFrame{m_modp, procp};
|
||||
auto r = m_frames.emplace(std::make_pair(procp, framep));
|
||||
UASSERT_OBJ(r.second, m_modp, "Procedure already contains a frame");
|
||||
return framep;
|
||||
}
|
||||
|
||||
void replaceWithMemberSel(AstVarRef* refp, const ForkDynScopeInstance& dynScope) {
|
||||
VNRelinker handle;
|
||||
refp->unlinkFrBack(&handle);
|
||||
AstMemberSel* const membersel = new AstMemberSel{
|
||||
refp->fileline(), new AstVarRef{refp->fileline(), dynScope.m_handlep, refp->access()},
|
||||
refp->dtypep()};
|
||||
membersel->name(refp->varp()->name());
|
||||
if (refp->varp()->direction() == VDirection::INPUT) {
|
||||
membersel->varp(
|
||||
VN_AS(m_memberMap.findMember(dynScope.m_classp, refp->varp()->name()), Var));
|
||||
} else {
|
||||
membersel->varp(refp->varp());
|
||||
}
|
||||
handle.relink(membersel);
|
||||
VL_DO_DANGLING(refp->deleteTree(), refp);
|
||||
}
|
||||
|
||||
static bool hasAsyncFork(AstNode* nodep) {
|
||||
bool afork = false;
|
||||
nodep->foreach([&](AstFork* forkp) {
|
||||
if (!forkp->joinType().join()) afork = true;
|
||||
});
|
||||
return afork;
|
||||
}
|
||||
|
||||
void bindNodeToDynScope(AstNode* nodep, ForkDynScopeFrame* frame) {
|
||||
m_frames.emplace(std::make_pair(nodep, frame));
|
||||
}
|
||||
|
||||
bool needsDynScope(const AstVarRef* refp) const {
|
||||
return
|
||||
// Can this variable escape the scope
|
||||
((m_forkDepth > refp->varp()->user1()) && refp->varp()->isFuncLocal())
|
||||
&& (
|
||||
// Is it mutated
|
||||
(refp->varp()->isClassHandleValue() ? refp->user2() : refp->access().isWriteOrRW())
|
||||
// Or is it after a timing-control event
|
||||
|| m_afterTimingControl);
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_modp);
|
||||
if (!VN_IS(nodep, Class)) m_modp = nodep;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstNodeFTask* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
if (hasAsyncFork(nodep)) pushDynScopeFrame(m_procp);
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstBegin* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
if (hasAsyncFork(nodep)) pushDynScopeFrame(m_procp);
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstFork* nodep) override {
|
||||
VL_RESTORER(m_forkDepth);
|
||||
if (!nodep->joinType().join()) ++m_forkDepth;
|
||||
|
||||
const bool oldAfterTimingControl = m_afterTimingControl;
|
||||
|
||||
ForkDynScopeFrame* framep = nullptr;
|
||||
if (nodep->initsp()) framep = pushDynScopeFrame(nodep);
|
||||
|
||||
for (AstNode* stmtp = nodep->initsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
if (AstVar* varp = VN_CAST(stmtp, Var)) {
|
||||
// This can be probably optimized to detect cases in which dynscopes
|
||||
// could be avoided
|
||||
if (!framep->instance().initialized()) framep->createInstancePrototype();
|
||||
framep->captureVarInsert(varp);
|
||||
bindNodeToDynScope(varp, framep);
|
||||
} else {
|
||||
AstAssign* const asgnp = VN_CAST(stmtp, Assign);
|
||||
UASSERT_OBJ(asgnp, stmtp,
|
||||
"Invalid node under block item initialization part of fork");
|
||||
bindNodeToDynScope(asgnp->lhsp(), framep);
|
||||
iterate(asgnp->rhsp());
|
||||
}
|
||||
}
|
||||
|
||||
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
m_afterTimingControl = false;
|
||||
iterate(stmtp);
|
||||
}
|
||||
m_afterTimingControl = oldAfterTimingControl;
|
||||
if (nodep->isTimingControl()) m_afterTimingControl = true;
|
||||
}
|
||||
void visit(AstNodeFTaskRef* nodep) override {
|
||||
visit(static_cast<AstNodeExpr*>(nodep));
|
||||
// We are before V3Timing, so unfortnately we need to treat any calls as suspending,
|
||||
// just to be safe. This might be improved if we could propagate suspendability
|
||||
// before doing all the other timing-related stuff.
|
||||
m_afterTimingControl = true;
|
||||
}
|
||||
void visit(AstVar* nodep) override {
|
||||
nodep->user1(m_forkDepth);
|
||||
ForkDynScopeFrame* const framep = frameOf(m_procp);
|
||||
if (!framep) return; // Cannot be legally referenced from a fork
|
||||
bindNodeToDynScope(nodep, framep);
|
||||
}
|
||||
void visit(AstVarRef* nodep) override {
|
||||
ForkDynScopeFrame* const framep = frameOf(nodep->varp());
|
||||
if (!framep) return;
|
||||
|
||||
if (needsDynScope(nodep)) {
|
||||
if (!framep->instance().initialized()) framep->createInstancePrototype();
|
||||
framep->captureVarInsert(nodep->varp());
|
||||
}
|
||||
bindNodeToDynScope(nodep, framep);
|
||||
}
|
||||
void visit(AstAssign* nodep) override {
|
||||
if (VN_IS(nodep->lhsp(), VarRef) && nodep->lhsp()->isClassHandleValue()) {
|
||||
nodep->lhsp()->user2(true);
|
||||
}
|
||||
visit(static_cast<AstNodeStmt*>(nodep));
|
||||
}
|
||||
void visit(AstNode* nodep) override {
|
||||
if (nodep->isTimingControl()) m_afterTimingControl = true;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit DynScopeVisitor(AstNetlist* nodep) {
|
||||
// Create Dynamic scope class prototypes and objects
|
||||
visit(nodep);
|
||||
|
||||
// Commit changes to AST
|
||||
bool typesAdded = false;
|
||||
for (auto frameIt : m_frames) {
|
||||
ForkDynScopeFrame* frame = frameIt.second;
|
||||
if (!frame->instance().initialized()) continue;
|
||||
|
||||
if (!frame->linked()) {
|
||||
frame->populateClass();
|
||||
frame->linkNodes(m_memberMap);
|
||||
typesAdded = true;
|
||||
}
|
||||
|
||||
if (AstVarRef* refp = VN_CAST(frameIt.first, VarRef)) {
|
||||
if (frame->captured(refp->varp())) replaceWithMemberSel(refp, frame->instance());
|
||||
}
|
||||
}
|
||||
|
||||
if (typesAdded) v3Global.rootp()->typeTablep()->repairCache();
|
||||
}
|
||||
~DynScopeVisitor() override = default;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Fork visitor, transforms asynchronous blocks into separate tasks
|
||||
|
||||
|
|
@ -59,10 +469,10 @@ private:
|
|||
AstVar* m_capturedVarsp = nullptr; // Local copies of captured variables
|
||||
std::set<AstVar*> m_forkLocalsp; // Variables local to a given fork
|
||||
AstArg* m_capturedVarRefsp = nullptr; // References to captured variables (as args)
|
||||
int m_createdTasksCount = 0; // Number of tasks created by this visitor
|
||||
|
||||
// METHODS
|
||||
AstVar* captureRef(AstNodeExpr* refp) {
|
||||
|
||||
AstVar* captureRef(AstVarRef* refp) {
|
||||
AstVar* varp = nullptr;
|
||||
for (varp = m_capturedVarsp; varp; varp = VN_AS(varp->nextp(), Var))
|
||||
if (varp->name() == refp->name()) break;
|
||||
|
|
@ -74,21 +484,20 @@ private:
|
|||
varp->lifetime(VLifetime::AUTOMATIC);
|
||||
m_capturedVarsp = AstNode::addNext(m_capturedVarsp, varp);
|
||||
// Use the original ref as an argument for call
|
||||
AstArg* arg = new AstArg{refp->fileline(), refp->name(), refp->cloneTree(false)};
|
||||
m_capturedVarRefsp = AstNode::addNext(m_capturedVarRefsp, arg);
|
||||
m_capturedVarRefsp
|
||||
= AstNode::addNext(m_capturedVarRefsp, new AstArg{refp->fileline(), refp->name(),
|
||||
refp->cloneTree(false)});
|
||||
}
|
||||
return varp;
|
||||
}
|
||||
|
||||
AstTask* makeTask(FileLine* fl, AstNode* stmtsp, std::string name) {
|
||||
AstTask* makeTask(FileLine* fl, AstNode* stmtsp, string name) {
|
||||
stmtsp = AstNode::addNext(static_cast<AstNode*>(m_capturedVarsp), stmtsp);
|
||||
AstTask* const taskp = new AstTask{fl, name, stmtsp};
|
||||
++m_createdTasksCount;
|
||||
return taskp;
|
||||
}
|
||||
|
||||
std::string generateTaskName(AstNode* fromp, std::string kind) {
|
||||
// TODO: Ensure no collisions occur
|
||||
string generateTaskName(AstNode* fromp, const string& kind) {
|
||||
return "__V" + kind + (!fromp->name().empty() ? (fromp->name() + "__") : "UNNAMED__")
|
||||
+ cvtToHex(fromp);
|
||||
}
|
||||
|
|
@ -122,16 +531,16 @@ private:
|
|||
|
||||
if (AstBegin* beginp = VN_CAST(nodep, Begin)) {
|
||||
UASSERT(beginp->stmtsp(), "No stmtsp\n");
|
||||
const std::string taskName = generateTaskName(beginp, "__FORK_BEGIN_");
|
||||
const string taskName = generateTaskName(beginp, "__FORK_BEGIN_");
|
||||
taskp
|
||||
= makeTask(beginp->fileline(), beginp->stmtsp()->unlinkFrBackWithNext(), taskName);
|
||||
beginp->unlinkFrBack(&handle);
|
||||
VL_DO_DANGLING(beginp->deleteTree(), beginp);
|
||||
} else if (AstNodeStmt* stmtp = VN_CAST(nodep, NodeStmt)) {
|
||||
const std::string taskName = generateTaskName(stmtp, "__FORK_STMT_");
|
||||
const string taskName = generateTaskName(stmtp, "__FORK_STMT_");
|
||||
taskp = makeTask(stmtp->fileline(), stmtp->unlinkFrBack(&handle), taskName);
|
||||
} else if (AstFork* forkp = VN_CAST(nodep, Fork)) {
|
||||
const std::string taskName = generateTaskName(forkp, "__FORK_NESTED_");
|
||||
const string taskName = generateTaskName(forkp, "__FORK_NESTED_");
|
||||
taskp = makeTask(forkp->fileline(), forkp->unlinkFrBack(&handle), taskName);
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +548,8 @@ private:
|
|||
|
||||
AstTaskRef* const taskrefp
|
||||
= new AstTaskRef{nodep->fileline(), taskp->name(), m_capturedVarRefsp};
|
||||
AstStmtExpr* const taskcallp = new AstStmtExpr{nodep->fileline(), taskrefp};
|
||||
taskrefp->taskp(taskp);
|
||||
AstStmtExpr* const taskcallp = taskrefp->makeStmt();
|
||||
// Replaced nodes will be revisited, so we don't need to "lift" the arguments
|
||||
// as captures in case of nested forks.
|
||||
handle.relink(taskcallp);
|
||||
|
|
@ -221,25 +631,21 @@ private:
|
|||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
ForkVisitor(AstNetlist* nodep) { visit(nodep); }
|
||||
explicit ForkVisitor(AstNetlist* nodep) { visit(nodep); }
|
||||
~ForkVisitor() override = default;
|
||||
|
||||
// UTILITY
|
||||
int createdTasksCount() { return m_createdTasksCount; }
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Fork class functions
|
||||
|
||||
int V3Fork::makeTasks(AstNetlist* nodep) {
|
||||
int createdTasksCount;
|
||||
|
||||
void V3Fork::makeDynamicScopes(AstNetlist* nodep) {
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
{
|
||||
ForkVisitor fork_visitor(nodep);
|
||||
createdTasksCount = fork_visitor.createdTasksCount();
|
||||
}
|
||||
V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3);
|
||||
|
||||
return createdTasksCount;
|
||||
{ DynScopeVisitor{nodep}; }
|
||||
V3Global::dumpCheckGlobalTree("fork_dynscope", 0, dumpTreeLevel() >= 3);
|
||||
}
|
||||
|
||||
void V3Fork::makeTasks(AstNetlist* nodep) {
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
{ ForkVisitor{nodep}; }
|
||||
V3Global::dumpCheckGlobalTree("fork", 0, dumpTreeLevel() >= 3);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@ class AstNetlist;
|
|||
|
||||
class V3Fork final {
|
||||
public:
|
||||
// Create tasks out of begin blocks that can outlive processes in which they were forked.
|
||||
// Move/copy variables to "anonymous" objects if their lifetime might exceed the scope of a
|
||||
// procedure that declared them. Update the references apropriately.
|
||||
static void makeDynamicScopes(AstNetlist* nodep);
|
||||
// Create tasks out of blocks/statments that can outlive processes in which they were forked.
|
||||
// Return value: number of tasks created
|
||||
static int makeTasks(AstNetlist* nodep);
|
||||
static void makeTasks(AstNetlist* nodep);
|
||||
};
|
||||
|
||||
#endif // Guard
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ public:
|
|||
// Support classes
|
||||
|
||||
class GateEitherVertex VL_NOT_FINAL : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(GateEitherVertex, V3GraphVertex)
|
||||
AstScope* const m_scopep; // Scope vertex refers to
|
||||
bool m_reducible = true; // True if this node should be able to be eliminated
|
||||
bool m_dedupable = true; // True if this node should be able to be deduped
|
||||
|
|
@ -122,6 +123,7 @@ public:
|
|||
};
|
||||
|
||||
class GateVarVertex final : public GateEitherVertex {
|
||||
VL_RTTI_IMPL(GateVarVertex, GateEitherVertex)
|
||||
AstVarScope* const m_varScp;
|
||||
bool m_isTop = false;
|
||||
bool m_isClock = false;
|
||||
|
|
@ -164,6 +166,7 @@ public:
|
|||
};
|
||||
|
||||
class GateLogicVertex final : public GateEitherVertex {
|
||||
VL_RTTI_IMPL(GateLogicVertex, GateEitherVertex)
|
||||
AstNode* const m_nodep;
|
||||
AstActive* const m_activep; // Under what active; nullptr is ok (under cfunc or such)
|
||||
const bool m_slow; // In slow block
|
||||
|
|
@ -331,7 +334,6 @@ private:
|
|||
AstActive* m_activep = nullptr; // Current active
|
||||
bool m_activeReducible = true; // Is activation block reducible?
|
||||
bool m_inSenItem = false; // Underneath AstSenItem; any varrefs are clocks
|
||||
bool m_inExprStmt = false; // Underneath ExprStmt; don't optimize LHS vars
|
||||
bool m_inSlow = false; // Inside a slow structure
|
||||
std::vector<AstNode*> m_optimized; // Logic blocks optimized
|
||||
|
||||
|
|
@ -497,10 +499,6 @@ private:
|
|||
// the weight will increase
|
||||
if (nodep->access().isWriteOrRW()) {
|
||||
new V3GraphEdge{&m_graph, m_logicVertexp, vvertexp, 1};
|
||||
if (m_inExprStmt) {
|
||||
m_logicVertexp->clearReducibleAndDedupable("LHS var in ExprStmt");
|
||||
m_logicVertexp->setConsumed("LHS var in ExprStmt");
|
||||
}
|
||||
}
|
||||
if (nodep->access().isReadOrRW()) {
|
||||
new V3GraphEdge{&m_graph, vvertexp, m_logicVertexp, 1};
|
||||
|
|
@ -516,11 +514,6 @@ private:
|
|||
iterateNewStmt(nodep, "User C Function", "User C Function");
|
||||
}
|
||||
void visit(AstClocking* nodep) override { iterateNewStmt(nodep, nullptr, nullptr); }
|
||||
void visit(AstExprStmt* nodep) override {
|
||||
VL_RESTORER(m_inExprStmt);
|
||||
m_inExprStmt = true;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstSenItem* nodep) override {
|
||||
VL_RESTORER(m_inSenItem);
|
||||
m_inSenItem = true;
|
||||
|
|
@ -578,7 +571,7 @@ public:
|
|||
|
||||
void GateVisitor::optimizeSignals(bool allowMultiIn) {
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(itp);
|
||||
GateVarVertex* const vvertexp = itp->cast<GateVarVertex>();
|
||||
|
||||
// Consider "inlining" variables
|
||||
if (!vvertexp) continue;
|
||||
|
|
@ -720,7 +713,7 @@ void GateVisitor::consumedMove() {
|
|||
// We need the "usually" block logic to do a better job at this
|
||||
for (V3GraphVertex* vertexp = m_graph.verticesBeginp(); vertexp;
|
||||
vertexp = vertexp->verticesNextp()) {
|
||||
if (const GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(vertexp)) {
|
||||
if (const GateVarVertex* const vvertexp = vertexp->cast<GateVarVertex>()) {
|
||||
if (!vvertexp->consumed() && !vvertexp->user()) {
|
||||
UINFO(8, "Unconsumed " << vvertexp->varScp() << endl);
|
||||
}
|
||||
|
|
@ -744,7 +737,7 @@ void GateVisitor::consumedMove() {
|
|||
void GateVisitor::warnSignals() {
|
||||
AstNode::user2ClearTree();
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (const GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(itp)) {
|
||||
if (const GateVarVertex* const vvertexp = itp->cast<GateVarVertex>()) {
|
||||
const AstVarScope* const vscp = vvertexp->varScp();
|
||||
const AstNode* const sp = vvertexp->rstSyncNodep();
|
||||
const AstNode* const ap = vvertexp->rstAsyncNodep();
|
||||
|
|
@ -905,6 +898,7 @@ class GateDedupeVarVisitor final : public VNVisitor {
|
|||
// (Note, the IF must be the only node under the always,
|
||||
// and the assign must be the only node under the if, other than the ifcond)
|
||||
// Any other ordering or node type, except for an AstComment, makes it not dedupable
|
||||
// AstExprStmt in the subtree of a node also makes the node not dedupable.
|
||||
private:
|
||||
// STATE
|
||||
GateDedupeHash m_ghash; // Hash used to find dupes of rhs of assign
|
||||
|
|
@ -920,6 +914,7 @@ private:
|
|||
// non-blocking statements, but erring on side of caution here
|
||||
if (!m_assignp) {
|
||||
m_assignp = assignp;
|
||||
m_dedupable = !assignp->exists([&](AstExprStmt*) { return true; });
|
||||
} else {
|
||||
m_dedupable = false;
|
||||
}
|
||||
|
|
@ -944,6 +939,7 @@ private:
|
|||
if (m_always && !m_ifCondp && !ifp->elsesp()) {
|
||||
// we're under an always, this is the first IF, and there's no else
|
||||
m_ifCondp = ifp->condp();
|
||||
m_dedupable = !m_ifCondp->exists([&](AstExprStmt*) { return true; });
|
||||
iterateAndNextNull(ifp->thensp());
|
||||
} else {
|
||||
m_dedupable = false;
|
||||
|
|
@ -1143,14 +1139,14 @@ void GateVisitor::dedupe() {
|
|||
// Traverse starting from each of the clocks
|
||||
UINFO(9, "Gate dedupe() clocks:\n");
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(itp)) {
|
||||
if (GateVarVertex* const vvertexp = itp->cast<GateVarVertex>()) {
|
||||
if (vvertexp->isClock()) deduper.dedupeTree(vvertexp);
|
||||
}
|
||||
}
|
||||
// Traverse starting from each of the outputs
|
||||
UINFO(9, "Gate dedupe() outputs:\n");
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(itp)) {
|
||||
if (GateVarVertex* const vvertexp = itp->cast<GateVarVertex>()) {
|
||||
if (vvertexp->isTop() && vvertexp->varScp()->varp()->isWritable()) {
|
||||
deduper.dedupeTree(vvertexp);
|
||||
}
|
||||
|
|
@ -1194,8 +1190,7 @@ private:
|
|||
for (V3GraphEdge* edgep = vvertexp->inBeginp(); edgep;) {
|
||||
V3GraphEdge* oldedgep = edgep;
|
||||
edgep = edgep->inNextp(); // for recursive since the edge could be deleted
|
||||
if (GateLogicVertex* const lvertexp
|
||||
= dynamic_cast<GateLogicVertex*>(oldedgep->fromp())) {
|
||||
if (GateLogicVertex* const lvertexp = oldedgep->fromp()->cast<GateLogicVertex>()) {
|
||||
if (AstNodeAssign* const assignp = VN_CAST(lvertexp->nodep(), NodeAssign)) {
|
||||
// if (lvertexp->outSize1() && VN_IS(assignp->lhsp(), Sel)) {
|
||||
if (VN_IS(assignp->lhsp(), Sel) && lvertexp->outSize1()) {
|
||||
|
|
@ -1281,7 +1276,7 @@ void GateVisitor::mergeAssigns() {
|
|||
UINFO(6, "mergeAssigns\n");
|
||||
GateMergeAssignsGraphVisitor merger{&m_graph};
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(itp)) {
|
||||
if (GateVarVertex* const vvertexp = itp->cast<GateVarVertex>()) {
|
||||
merger.mergeAssignsTree(vvertexp);
|
||||
}
|
||||
}
|
||||
|
|
@ -1467,7 +1462,7 @@ void GateVisitor::decomposeClkVectors() {
|
|||
AstNode::user2ClearTree();
|
||||
GateClkDecompGraphVisitor decomposer{&m_graph};
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (GateVarVertex* const vertp = dynamic_cast<GateVarVertex*>(itp)) {
|
||||
if (GateVarVertex* const vertp = itp->cast<GateVarVertex>()) {
|
||||
const AstVarScope* const vsp = vertp->varScp();
|
||||
if (vsp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) {
|
||||
if (vsp->varp()->width() > 1) {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class V3Global final {
|
|||
bool m_assertDTypesResolved = false; // Tree should have dtypep()'s
|
||||
bool m_assertScoped = false; // Tree is scoped
|
||||
bool m_constRemoveXs = false; // Const needs to strip any Xs
|
||||
// Experimenting with always requiring heavy, see (#2701)
|
||||
// Experimenting with always requiring heavy, see issue #2701
|
||||
bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols
|
||||
bool m_dpi = false; // Need __Dpi include files
|
||||
bool m_hasEvents = false; // Design uses SystemVerilog named events
|
||||
|
|
|
|||
|
|
@ -92,26 +92,6 @@ void V3GraphVertex::rerouteEdges(V3Graph* graphp) {
|
|||
bool V3GraphVertex::inSize1() const { return !inEmpty() && !inBeginp()->inNextp(); }
|
||||
bool V3GraphVertex::outSize1() const { return !outEmpty() && !outBeginp()->outNextp(); }
|
||||
|
||||
uint32_t V3GraphVertex::inHash() const {
|
||||
// We want the same hash ignoring the order of edges.
|
||||
// So we need an associative operator, like XOR.
|
||||
// However with XOR multiple edges to the same source will cancel out,
|
||||
// so we use ADD. (Generally call this only after removing duplicates though)
|
||||
uint32_t hash = 0;
|
||||
for (V3GraphEdge* edgep = this->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
hash += cvtToHash(edgep->fromp());
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t V3GraphVertex::outHash() const {
|
||||
uint32_t hash = 0;
|
||||
for (V3GraphEdge* edgep = this->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
hash += cvtToHash(edgep->top());
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
V3GraphEdge* V3GraphVertex::findConnectingEdgep(GraphWay way, const V3GraphVertex* waywardp) {
|
||||
// O(edges) linear search. Searches search both nodes' edge lists in
|
||||
// parallel. The lists probably aren't _both_ huge, so this is
|
||||
|
|
@ -129,7 +109,7 @@ V3GraphEdge* V3GraphVertex::findConnectingEdgep(GraphWay way, const V3GraphVerte
|
|||
}
|
||||
|
||||
// cppcheck-has-bug-suppress constParameter
|
||||
void V3GraphVertex::v3errorEnd(std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
void V3GraphVertex::v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
|
||||
std::ostringstream nsstr;
|
||||
nsstr << str.str();
|
||||
if (debug()) {
|
||||
|
|
@ -139,11 +119,11 @@ void V3GraphVertex::v3errorEnd(std::ostringstream& str) const VL_REQUIRES(V3Erro
|
|||
if (FileLine* const flp = fileline()) {
|
||||
flp->v3errorEnd(nsstr);
|
||||
} else {
|
||||
V3Error::s().v3errorEnd(nsstr);
|
||||
V3Error::v3errorEnd(nsstr);
|
||||
}
|
||||
}
|
||||
void V3GraphVertex::v3errorEndFatal(std::ostringstream& str) const
|
||||
VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
VL_RELEASE(V3Error::s().m_mutex) {
|
||||
v3errorEnd(str);
|
||||
assert(0); // LCOV_EXCL_LINE
|
||||
VL_UNREACHABLE;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "V3Error.h"
|
||||
#include "V3List.h"
|
||||
#include "V3Rtti.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -173,6 +174,7 @@ public:
|
|||
//============================================================================
|
||||
|
||||
class V3GraphVertex VL_NOT_FINAL {
|
||||
VL_RTTI_IMPL_BASE(V3GraphVertex)
|
||||
// Vertices may be a 'gate'/wire statement OR a variable
|
||||
protected:
|
||||
friend class V3Graph;
|
||||
|
|
@ -209,6 +211,40 @@ public:
|
|||
void unlinkEdges(V3Graph* graphp);
|
||||
void unlinkDelete(V3Graph* graphp);
|
||||
|
||||
// METHODS
|
||||
// Return true iff of type T
|
||||
template <typename T>
|
||||
bool is() const {
|
||||
static_assert(std::is_base_of<V3GraphVertex, T>::value,
|
||||
"'T' must be a subtype of V3GraphVertex");
|
||||
static_assert(std::is_same<typename std::remove_cv<T>::type,
|
||||
VTypeListFront<typename T::RttiThisAndBaseClassesList>>::value,
|
||||
"Missing VL_RTTI_IMPL(...) call in 'T'");
|
||||
return this->isInstanceOfClassWithId(T::rttiClassId());
|
||||
}
|
||||
|
||||
// Return cast to subtype T and assert of that type
|
||||
template <typename T>
|
||||
T* as() {
|
||||
UASSERT_OBJ(is<T>(), this, "V3GraphVertex is not of expected type");
|
||||
return static_cast<T*>(this);
|
||||
}
|
||||
template <typename T>
|
||||
const T* as() const {
|
||||
UASSERT_OBJ(is<T>(), this, "V3GraphVertex is not of expected type");
|
||||
return static_cast<const T*>(this);
|
||||
}
|
||||
|
||||
// Return cast to subtype T, else nullptr if different type
|
||||
template <typename T>
|
||||
T* cast() {
|
||||
return is<T>() ? static_cast<T*>(this) : nullptr;
|
||||
}
|
||||
template <typename T>
|
||||
const T* cast() const {
|
||||
return is<T>() ? static_cast<const T*>(this) : nullptr;
|
||||
}
|
||||
|
||||
// ACCESSORS
|
||||
virtual string name() const { return ""; }
|
||||
virtual string dotColor() const { return "black"; }
|
||||
|
|
@ -240,16 +276,14 @@ public:
|
|||
V3GraphEdge* inBeginp() const { return m_ins.begin(); }
|
||||
bool inEmpty() const { return inBeginp() == nullptr; }
|
||||
bool inSize1() const;
|
||||
uint32_t inHash() const;
|
||||
V3GraphEdge* outBeginp() const { return m_outs.begin(); }
|
||||
bool outEmpty() const { return outBeginp() == nullptr; }
|
||||
bool outSize1() const;
|
||||
uint32_t outHash() const;
|
||||
V3GraphEdge* beginp(GraphWay way) const { return way.forward() ? outBeginp() : inBeginp(); }
|
||||
// METHODS
|
||||
/// Error reporting
|
||||
void v3errorEnd(std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex);
|
||||
void v3errorEndFatal(std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex);
|
||||
void v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex);
|
||||
void v3errorEndFatal(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex);
|
||||
/// Edges are routed around this vertex to point from "from" directly to "to"
|
||||
void rerouteEdges(V3Graph* graphp);
|
||||
/// Find the edge connecting ap and bp, where bp is wayward from ap.
|
||||
|
|
@ -262,6 +296,7 @@ std::ostream& operator<<(std::ostream& os, V3GraphVertex* vertexp);
|
|||
//============================================================================
|
||||
|
||||
class V3GraphEdge VL_NOT_FINAL {
|
||||
VL_RTTI_IMPL_BASE(V3GraphEdge)
|
||||
// Wires/variables aren't edges. Edges have only a single to/from vertex
|
||||
public:
|
||||
// ENUMS
|
||||
|
|
@ -308,6 +343,39 @@ public:
|
|||
}
|
||||
virtual ~V3GraphEdge() = default;
|
||||
// METHODS
|
||||
// Return true iff of type T
|
||||
template <typename T>
|
||||
bool is() const {
|
||||
static_assert(std::is_base_of<V3GraphEdge, T>::value,
|
||||
"'T' must be a subtype of V3GraphEdge");
|
||||
static_assert(std::is_same<typename std::remove_cv<T>::type,
|
||||
VTypeListFront<typename T::RttiThisAndBaseClassesList>>::value,
|
||||
"Missing VL_RTTI_IMPL(...) call in 'T'");
|
||||
return this->isInstanceOfClassWithId(T::rttiClassId());
|
||||
}
|
||||
|
||||
// Return cast to subtype T and assert of that type
|
||||
template <typename T>
|
||||
T* as() {
|
||||
UASSERT(is<T>(), "V3GraphEdge is not of expected type");
|
||||
return static_cast<T*>(this);
|
||||
}
|
||||
template <typename T>
|
||||
const T* as() const {
|
||||
UASSERT(is<T>(), "V3GraphEdge is not of expected type");
|
||||
return static_cast<const T*>(this);
|
||||
}
|
||||
|
||||
// Return cast to subtype T, else nullptr if different type
|
||||
template <typename T>
|
||||
T* cast() {
|
||||
return is<T>() ? static_cast<T*>(this) : nullptr;
|
||||
}
|
||||
template <typename T>
|
||||
const T* cast() const {
|
||||
return is<T>() ? static_cast<const T*>(this) : nullptr;
|
||||
}
|
||||
|
||||
virtual string name() const { return m_fromp->name() + "->" + m_top->name(); }
|
||||
virtual string dotLabel() const { return ""; }
|
||||
virtual string dotColor() const { return cutable() ? "yellowGreen" : "red"; }
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
|||
// Break the minimal number of backward edges to make the graph acyclic
|
||||
|
||||
class GraphAcycVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(GraphAcycVertex, V3GraphVertex)
|
||||
// user() is used for various sub-algorithm pieces
|
||||
V3GraphVertex* const m_origVertexp; // Pointer to first vertex this represents
|
||||
protected:
|
||||
|
|
@ -56,6 +57,7 @@ public:
|
|||
//--------------------------------------------------------------------
|
||||
|
||||
class GraphAcycEdge final : public V3GraphEdge {
|
||||
VL_RTTI_IMPL(GraphAcycEdge, V3GraphEdge)
|
||||
// userp() is always used to point to the head original graph edge
|
||||
private:
|
||||
using OrigEdgeList = std::list<V3GraphEdge*>; // List of orig edges, see also GraphAcyc's decl
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public:
|
|||
// Vertices and nodes
|
||||
|
||||
class V3GraphTestVertex VL_NOT_FINAL : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(V3GraphTestVertex, V3GraphVertex)
|
||||
const string m_name;
|
||||
|
||||
public:
|
||||
|
|
@ -293,10 +294,8 @@ public:
|
|||
void V3Graph::selfTest() {
|
||||
// Execute all of the tests
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
// clang-format off
|
||||
{ V3GraphTestStrong{}.run(); }
|
||||
{ V3GraphTestAcyc{}.run(); }
|
||||
{ V3GraphTestVars{}.run(); }
|
||||
{ V3GraphTestImport{}.run(); }
|
||||
// clang-format on
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,9 @@ private:
|
|||
iterateConstNull(nodep->refDTypep());
|
||||
});
|
||||
}
|
||||
void visit(AstStreamDType* nodep) override {
|
||||
m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() {});
|
||||
}
|
||||
void visit(AstVoidDType* nodep) override {
|
||||
m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() {});
|
||||
}
|
||||
|
|
@ -207,7 +210,7 @@ private:
|
|||
iterateConstNull(nodep->varScopep());
|
||||
} else {
|
||||
iterateConstNull(nodep->varp());
|
||||
m_hash += nodep->selfPointer();
|
||||
m_hash += nodep->selfPointer().asString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -269,6 +272,9 @@ private:
|
|||
iterateConstNull(nodep->sensesp());
|
||||
});
|
||||
}
|
||||
void visit(AstCLocalScope* nodep) override {
|
||||
m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, HASH_CHILDREN, [=]() {});
|
||||
}
|
||||
void visit(AstCoverInc* nodep) override {
|
||||
m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() { //
|
||||
iterateConstNull(nodep->declp());
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ private:
|
|||
// UINFO(4, " CFUNC " << nodep << endl);
|
||||
if (!m_tracingCall && !nodep->entryPoint()) return;
|
||||
m_tracingCall = false;
|
||||
if (nodep->dpiImportPrototype() && !nodep->pure()) {
|
||||
if (nodep->dpiImportPrototype() && !nodep->dpiPure()) {
|
||||
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ private:
|
|||
}
|
||||
for (V3GraphVertex* mtaskVxp = nodep->depGraphp()->verticesBeginp(); mtaskVxp;
|
||||
mtaskVxp = mtaskVxp->verticesNextp()) {
|
||||
const ExecMTask* const mtaskp = dynamic_cast<ExecMTask*>(mtaskVxp);
|
||||
const ExecMTask* const mtaskp = mtaskVxp->as<ExecMTask>();
|
||||
m_execMTaskp = mtaskp;
|
||||
m_sequence = 0;
|
||||
iterate(mtaskp->bodyp());
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public:
|
|||
};
|
||||
|
||||
class LinkCellsVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(LinkCellsVertex, V3GraphVertex)
|
||||
AstNodeModule* const m_modp;
|
||||
|
||||
public:
|
||||
|
|
@ -69,6 +70,7 @@ public:
|
|||
};
|
||||
|
||||
class LibraryVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(LibraryVertex, V3GraphVertex)
|
||||
public:
|
||||
explicit LibraryVertex(V3Graph* graphp)
|
||||
: V3GraphVertex{graphp} {}
|
||||
|
|
@ -77,7 +79,7 @@ public:
|
|||
};
|
||||
|
||||
void LinkCellsGraph::loopsMessageCb(V3GraphVertex* vertexp) {
|
||||
if (const LinkCellsVertex* const vvertexp = dynamic_cast<LinkCellsVertex*>(vertexp)) {
|
||||
if (const LinkCellsVertex* const vvertexp = vertexp->cast<LinkCellsVertex>()) {
|
||||
vvertexp->modp()->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: Recursive multiple modules (module instantiates "
|
||||
"something leading back to itself): "
|
||||
|
|
@ -171,7 +173,7 @@ private:
|
|||
if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed("linkcells");
|
||||
m_graph.rank();
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (const LinkCellsVertex* const vvertexp = dynamic_cast<LinkCellsVertex*>(itp)) {
|
||||
if (const LinkCellsVertex* const vvertexp = itp->cast<LinkCellsVertex>()) {
|
||||
// +1 so we leave level 1 for the new wrapper we'll make in a moment
|
||||
AstNodeModule* const modp = vvertexp->modp();
|
||||
modp->level(vvertexp->rank() + 1);
|
||||
|
|
|
|||
|
|
@ -1121,9 +1121,9 @@ class LinkDotFindVisitor final : public VNVisitor {
|
|||
m_statep->insertSym(m_curSymp, newvarp->name(), newvarp,
|
||||
nullptr /*classOrPackagep*/);
|
||||
}
|
||||
VL_RESTORER(m_ftaskp);
|
||||
m_ftaskp = nodep;
|
||||
iterateChildren(nodep);
|
||||
m_ftaskp = nullptr;
|
||||
}
|
||||
}
|
||||
void visit(AstClocking* nodep) override {
|
||||
|
|
@ -1351,7 +1351,7 @@ class LinkDotFindVisitor final : public VNVisitor {
|
|||
if (!foundp && m_modSymp && nodep->name() == m_modSymp->nodep()->name()) {
|
||||
foundp = m_modSymp; // Conflicts with modname?
|
||||
}
|
||||
AstEnumItem* const findvarp = foundp ? VN_AS(foundp->nodep(), EnumItem) : nullptr;
|
||||
AstEnumItem* const findvarp = foundp ? VN_CAST(foundp->nodep(), EnumItem) : nullptr;
|
||||
bool ins = false;
|
||||
if (!foundp) {
|
||||
ins = true;
|
||||
|
|
@ -1375,7 +1375,7 @@ class LinkDotFindVisitor final : public VNVisitor {
|
|||
<< nodep->warnContextPrimary() << '\n'
|
||||
<< foundp->nodep()->warnOther()
|
||||
<< "... Location of original declaration\n"
|
||||
<< nodep->warnContextSecondary());
|
||||
<< foundp->nodep()->warnContextSecondary());
|
||||
}
|
||||
ins = true;
|
||||
}
|
||||
|
|
@ -1616,6 +1616,7 @@ private:
|
|||
AstPin* const pinp = new AstPin{nodep->fileline(),
|
||||
-1, // Pin# not relevant
|
||||
nodep->name(), exprp};
|
||||
pinp->param(true);
|
||||
cellp->addParamsp(pinp);
|
||||
}
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
|
|
@ -2245,11 +2246,30 @@ private:
|
|||
}
|
||||
}
|
||||
void importSymbolsFromExtended(AstClass* const nodep, AstClassExtends* const cextp) {
|
||||
AstClass* const classp = cextp->classp();
|
||||
VSymEnt* const srcp = m_statep->getNodeSym(classp);
|
||||
if (classp->isInterfaceClass()) importImplementsClass(nodep, srcp, classp);
|
||||
AstClass* const baseClassp = cextp->classp();
|
||||
VSymEnt* const srcp = m_statep->getNodeSym(baseClassp);
|
||||
if (baseClassp->isInterfaceClass()) importImplementsClass(nodep, srcp, baseClassp);
|
||||
if (!cextp->isImplements()) m_curSymp->importFromClass(m_statep->symsp(), srcp);
|
||||
}
|
||||
void classExtendImport(AstClass* nodep) {
|
||||
// A class reference might be to a class that is later in Ast due to
|
||||
// e.g. parmaeterization or referring to a "class (type T) extends T"
|
||||
// Resolve it so later Class:: references into its base classes work
|
||||
VL_RESTORER(m_ds);
|
||||
VSymEnt* const srcp = m_statep->getNodeSym(nodep);
|
||||
m_ds.init(srcp);
|
||||
iterate(nodep);
|
||||
}
|
||||
bool checkPinRef(AstPin* pinp, VVarType refVarType) {
|
||||
// In instantiations of modules/ifaces, we shouldn't connect port pins to submodule's
|
||||
// parameters or vice versa
|
||||
return pinp->param() == refVarType.isParam();
|
||||
}
|
||||
void updateVarUse(AstVar* nodep) {
|
||||
// Avoid dotted.PARAM false positive when in a parameter block
|
||||
// that is if ()'ed off by same dotted name as another block
|
||||
if (nodep && nodep->isParam()) nodep->usedParam(true);
|
||||
}
|
||||
|
||||
// VISITs
|
||||
void visit(AstNetlist* nodep) override {
|
||||
|
|
@ -2344,7 +2364,32 @@ private:
|
|||
UASSERT_OBJ(m_pinSymp, nodep, "Pin not under instance?");
|
||||
VSymEnt* const foundp = m_pinSymp->findIdFlat(nodep->name());
|
||||
const char* const whatp = nodep->param() ? "parameter pin" : "pin";
|
||||
if (!foundp) {
|
||||
bool pinCheckFail = false;
|
||||
if (foundp) {
|
||||
if (AstVar* const refp = VN_CAST(foundp->nodep(), Var)) {
|
||||
if (!refp->isIO() && !refp->isParam() && !refp->isIfaceRef()) {
|
||||
nodep->v3error(ucfirst(whatp)
|
||||
<< " is not an in/out/inout/param/interface: "
|
||||
<< nodep->prettyNameQ());
|
||||
} else if (!checkPinRef(nodep, refp->varType())) {
|
||||
pinCheckFail = true;
|
||||
} else {
|
||||
nodep->modVarp(refp);
|
||||
markAndCheckPinDup(nodep, refp, whatp);
|
||||
}
|
||||
} else if (AstParamTypeDType* const refp
|
||||
= VN_CAST(foundp->nodep(), ParamTypeDType)) {
|
||||
if (!checkPinRef(nodep, refp->varType())) {
|
||||
pinCheckFail = true;
|
||||
} else {
|
||||
nodep->modPTypep(refp);
|
||||
markAndCheckPinDup(nodep, refp, whatp);
|
||||
}
|
||||
} else {
|
||||
nodep->v3error(ucfirst(whatp) << " not found: " << nodep->prettyNameQ());
|
||||
}
|
||||
}
|
||||
if (!foundp || pinCheckFail) {
|
||||
if (nodep->name() == "__paramNumber1" && m_cellp
|
||||
&& VN_IS(m_cellp->modp(), Primitive)) {
|
||||
// Primitive parameter is really a delay we can just ignore
|
||||
|
|
@ -2360,19 +2405,6 @@ private:
|
|||
ucfirst(whatp)
|
||||
<< " not found: " << nodep->prettyNameQ() << '\n'
|
||||
<< (suggest.empty() ? "" : nodep->warnMore() + suggest));
|
||||
} else if (AstVar* const refp = VN_CAST(foundp->nodep(), Var)) {
|
||||
if (!refp->isIO() && !refp->isParam() && !refp->isIfaceRef()) {
|
||||
nodep->v3error(ucfirst(whatp) << " is not an in/out/inout/param/interface: "
|
||||
<< nodep->prettyNameQ());
|
||||
} else {
|
||||
nodep->modVarp(refp);
|
||||
markAndCheckPinDup(nodep, refp, whatp);
|
||||
}
|
||||
} else if (AstParamTypeDType* const refp = VN_CAST(foundp->nodep(), ParamTypeDType)) {
|
||||
nodep->modPTypep(refp);
|
||||
markAndCheckPinDup(nodep, refp, whatp);
|
||||
} else {
|
||||
nodep->v3error(ucfirst(whatp) << " not found: " << nodep->prettyNameQ());
|
||||
}
|
||||
}
|
||||
// Early return() above when deleted
|
||||
|
|
@ -2422,9 +2454,9 @@ private:
|
|||
} else {
|
||||
const auto cextp = classp->extendsp();
|
||||
UASSERT_OBJ(cextp, nodep, "Bad super extends link");
|
||||
const auto sclassp = cextp->classp();
|
||||
UASSERT_OBJ(sclassp, nodep, "Bad superclass");
|
||||
m_ds.m_dotSymp = m_statep->getNodeSym(sclassp);
|
||||
const auto baseClassp = cextp->classp();
|
||||
UASSERT_OBJ(baseClassp, nodep, "Bad superclass");
|
||||
m_ds.m_dotSymp = m_statep->getNodeSym(baseClassp);
|
||||
UINFO(8, " super. " << m_ds.ascii() << endl);
|
||||
}
|
||||
}
|
||||
|
|
@ -2555,13 +2587,15 @@ private:
|
|||
string expectWhat;
|
||||
bool allowScope = false;
|
||||
bool allowVar = false;
|
||||
bool allowFTask = false;
|
||||
bool staticAccess = false;
|
||||
if (m_ds.m_dotPos == DP_PACKAGE) {
|
||||
// {package}::{a}
|
||||
AstNodeModule* classOrPackagep = nullptr;
|
||||
expectWhat = "scope/variable";
|
||||
expectWhat = "scope/variable/func";
|
||||
allowScope = true;
|
||||
allowVar = true;
|
||||
allowFTask = true;
|
||||
staticAccess = true;
|
||||
UASSERT_OBJ(VN_IS(m_ds.m_dotp->lhsp(), ClassOrPackageRef), m_ds.m_dotp->lhsp(),
|
||||
"Bad package link");
|
||||
|
|
@ -2642,6 +2676,13 @@ private:
|
|||
<< cellp->modp()->prettyNameQ());
|
||||
}
|
||||
}
|
||||
} else if (allowFTask && VN_IS(foundp->nodep(), NodeFTask)) {
|
||||
AstTaskRef* const taskrefp
|
||||
= new AstTaskRef{nodep->fileline(), nodep->name(), nullptr};
|
||||
nodep->replaceWith(taskrefp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
if (start) m_ds = lastStates;
|
||||
return;
|
||||
} else if (AstVar* const varp = foundToVarp(foundp, nodep, VAccess::READ)) {
|
||||
AstIfaceRefDType* const ifacerefp
|
||||
= LinkDotState::ifaceRefFromArray(varp->subDTypep());
|
||||
|
|
@ -2864,19 +2905,26 @@ private:
|
|||
"class reference parameter not removed by V3Param");
|
||||
VL_RESTORER(m_ds);
|
||||
VL_RESTORER(m_pinSymp);
|
||||
{
|
||||
// ClassRef's have pins, so track
|
||||
if (nodep->classOrPackagep()) {
|
||||
m_pinSymp = m_statep->getNodeSym(nodep->classOrPackagep());
|
||||
}
|
||||
m_ds.init(m_curSymp);
|
||||
UINFO(4, "(Backto) Link ClassOrPackageRef: " << nodep << endl);
|
||||
iterateChildren(nodep);
|
||||
|
||||
// ClassRef's have pins, so track
|
||||
if (nodep->classOrPackagep()) {
|
||||
m_pinSymp = m_statep->getNodeSym(nodep->classOrPackagep());
|
||||
}
|
||||
if (m_statep->forPrimary() && VN_IS(nodep->classOrPackagep(), Class) && !nodep->paramsp()
|
||||
AstClass* const refClassp = VN_CAST(nodep->classOrPackagep(), Class);
|
||||
// Make sure any extends() are properly imported within referenced class
|
||||
if (refClassp && !m_statep->forPrimary()) classExtendImport(refClassp);
|
||||
|
||||
m_ds.init(m_curSymp);
|
||||
UINFO(4, "(Backto) Link ClassOrPackageRef: " << nodep << endl);
|
||||
iterateChildren(nodep);
|
||||
|
||||
AstClass* const modClassp = VN_CAST(m_modp, Class);
|
||||
if (m_statep->forPrimary() && refClassp && !nodep->paramsp()
|
||||
&& nodep->classOrPackagep()->hasGParam()
|
||||
// Don't warn on typedefs, which are hard to know if there's a param somewhere buried
|
||||
&& VN_IS(nodep->classOrPackageNodep(), Class)) {
|
||||
&& VN_IS(nodep->classOrPackageNodep(), Class)
|
||||
// References to class:: within class itself are OK per IEEE (UVM does this)
|
||||
&& modClassp != refClassp) {
|
||||
nodep->v3error("Reference to parameterized class without #() (IEEE 1800-2017 8.25.1)\n"
|
||||
<< nodep->warnMore() << "... Suggest use '"
|
||||
<< nodep->classOrPackageNodep()->prettyName() << "#()'");
|
||||
|
|
@ -2895,6 +2943,7 @@ private:
|
|||
if (AstVar* const varp
|
||||
= foundp ? foundToVarp(foundp, nodep, nodep->access()) : nullptr) {
|
||||
nodep->varp(varp);
|
||||
updateVarUse(nodep->varp());
|
||||
// Generally set by parse, but might be an import
|
||||
nodep->classOrPackagep(foundp->classOrPackagep());
|
||||
}
|
||||
|
|
@ -2937,6 +2986,7 @@ private:
|
|||
AstVar* const varp
|
||||
= foundp ? foundToVarp(foundp, nodep, nodep->access()) : nullptr;
|
||||
nodep->varp(varp);
|
||||
updateVarUse(nodep->varp());
|
||||
UINFO(7, " Resolved " << nodep << endl); // Also prints varp
|
||||
if (!nodep->varp()) {
|
||||
nodep->v3error("Can't find definition of "
|
||||
|
|
@ -2974,6 +3024,7 @@ private:
|
|||
// later optimizations to deal with VarXRef.
|
||||
nodep->varp(vscp->varp());
|
||||
nodep->varScopep(vscp);
|
||||
updateVarUse(nodep->varp());
|
||||
UINFO(7, " Resolved " << nodep << endl); // Also prints taskp
|
||||
AstVarRef* const newvscp
|
||||
= new AstVarRef{nodep->fileline(), vscp, nodep->access()};
|
||||
|
|
@ -3467,26 +3518,28 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
if (AstClass* const classp = cextp->classOrNullp()) {
|
||||
if (AstClass* const baseClassp = cextp->classOrNullp()) {
|
||||
// Already converted. Update symbol table to link unlinked members.
|
||||
// Base class has to be visited in a case if its extends statement
|
||||
// needs to be handled. Recursive inheritance was already checked.
|
||||
if (classp == nodep) {
|
||||
// Must be here instead of in LinkDotParam to handle
|
||||
// "class (type T) extends T".
|
||||
if (baseClassp == nodep) {
|
||||
cextp->v3error("Attempting to extend class " << nodep->prettyNameQ()
|
||||
<< " from itself");
|
||||
} else if (cextp->isImplements() && !classp->isInterfaceClass()) {
|
||||
} else if (cextp->isImplements() && !baseClassp->isInterfaceClass()) {
|
||||
cextp->v3error("Attempting to implement from non-interface class "
|
||||
<< classp->prettyNameQ() << '\n'
|
||||
<< baseClassp->prettyNameQ() << '\n'
|
||||
<< "... Suggest use 'extends'");
|
||||
} else if (!cextp->isImplements() && !nodep->isInterfaceClass()
|
||||
&& classp->isInterfaceClass()) {
|
||||
&& baseClassp->isInterfaceClass()) {
|
||||
cextp->v3error("Attempting to extend from interface class "
|
||||
<< classp->prettyNameQ() << '\n'
|
||||
<< baseClassp->prettyNameQ() << '\n'
|
||||
<< "... Suggest use 'implements'");
|
||||
}
|
||||
classp->isExtended(true);
|
||||
baseClassp->isExtended(true);
|
||||
nodep->isExtended(true);
|
||||
iterate(classp);
|
||||
iterate(baseClassp);
|
||||
importSymbolsFromExtended(nodep, cextp);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -3518,7 +3571,8 @@ private:
|
|||
if (nodep->user3SetOnce()) return;
|
||||
if (AstNode* const cpackagep = nodep->classOrPackageOpp()) {
|
||||
if (AstClassOrPackageRef* const cpackagerefp = VN_CAST(cpackagep, ClassOrPackageRef)) {
|
||||
if (cpackagerefp->paramsp()) {
|
||||
const AstClass* const clsp = VN_CAST(cpackagerefp->classOrPackageNodep(), Class);
|
||||
if (clsp && clsp->isParameterized()) {
|
||||
// Unable to link before the instantiation of parameter classes.
|
||||
// The class reference node has to be visited to properly link parameters.
|
||||
iterate(cpackagep);
|
||||
|
|
@ -3702,6 +3756,7 @@ void V3LinkDot::linkDotGuts(AstNetlist* rootp, VLinkDotStep step) {
|
|||
state.computeScopeAliases();
|
||||
state.dumpSelf();
|
||||
{ LinkDotResolveVisitor{rootp, &state}; }
|
||||
state.dumpSelf();
|
||||
}
|
||||
|
||||
void V3LinkDot::linkDotPrimary(AstNetlist* nodep) {
|
||||
|
|
|
|||
|
|
@ -61,13 +61,30 @@ private:
|
|||
|
||||
// STATE
|
||||
AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside
|
||||
AstNodeModule* m_modp = nullptr; // Module we're inside
|
||||
int m_modIncrementsNum = 0; // Var name counter
|
||||
InsertMode m_insMode = IM_BEFORE; // How to insert
|
||||
AstNode* m_insStmtp = nullptr; // Where to insert statement
|
||||
bool m_unsupportedHere = false; // Used to detect where it's not supported yet
|
||||
|
||||
// METHODS
|
||||
void insertBeforeStmt(AstNode* nodep, AstNode* newp) {
|
||||
void insertOnTop(AstNode* newp) {
|
||||
// Add the thing directly under the current TFunc/Module
|
||||
AstNode* stmtsp = nullptr;
|
||||
if (m_ftaskp) {
|
||||
stmtsp = m_ftaskp->stmtsp();
|
||||
} else if (m_modp) {
|
||||
stmtsp = m_modp->stmtsp();
|
||||
}
|
||||
UASSERT(stmtsp, "Variable not under FTASK/MODULE");
|
||||
newp->addNext(stmtsp->unlinkFrBackWithNext());
|
||||
if (m_ftaskp) {
|
||||
m_ftaskp->addStmtsp(newp);
|
||||
} else if (m_modp) {
|
||||
m_modp->addStmtsp(newp);
|
||||
}
|
||||
}
|
||||
void insertNextToStmt(AstNode* nodep, AstNode* newp) {
|
||||
// Return node that must be visited, if any
|
||||
if (debug() >= 9) newp->dumpTree("- newstmt: ");
|
||||
UASSERT_OBJ(m_insStmtp, nodep, "Function not underneath a statement");
|
||||
|
|
@ -88,7 +105,9 @@ private:
|
|||
|
||||
// VISITORS
|
||||
void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_modp);
|
||||
VL_RESTORER(m_modIncrementsNum);
|
||||
m_modp = nodep;
|
||||
m_modIncrementsNum = 0;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
|
@ -244,15 +263,14 @@ private:
|
|||
return;
|
||||
}
|
||||
AstNodeExpr* const readp = nodep->rhsp();
|
||||
AstNodeExpr* const writep = nodep->thsp();
|
||||
AstNodeExpr* const writep = nodep->thsp()->unlinkFrBack();
|
||||
|
||||
AstConst* const constp = VN_AS(nodep->lhsp(), Const);
|
||||
UASSERT_OBJ(nodep, constp, "Expecting CONST");
|
||||
const AstNode* const backp = nodep->backp();
|
||||
AstConst* const newconstp = constp->cloneTree(true);
|
||||
|
||||
// Prepare a temporary variable
|
||||
FileLine* const fl = backp->fileline();
|
||||
FileLine* const fl = nodep->fileline();
|
||||
const string name = string{"__Vincrement"} + cvtToStr(++m_modIncrementsNum);
|
||||
AstVar* const varp = new AstVar{
|
||||
fl, VVarType::BLOCKTEMP, name, VFlagChildDType{},
|
||||
|
|
@ -260,7 +278,7 @@ private:
|
|||
if (m_ftaskp) varp->funcLocal(true);
|
||||
|
||||
// Declare the variable
|
||||
insertBeforeStmt(nodep, varp);
|
||||
insertOnTop(varp);
|
||||
|
||||
// Define what operation will we be doing
|
||||
AstNodeExpr* operp;
|
||||
|
|
@ -273,17 +291,20 @@ private:
|
|||
if (VN_IS(nodep, PreAdd) || VN_IS(nodep, PreSub)) {
|
||||
// PreAdd/PreSub operations
|
||||
// Immediately after declaration - increment it by one
|
||||
varp->addNextHere(new AstAssign{fl, writep->cloneTree(true),
|
||||
new AstVarRef{fl, varp, VAccess::READ}});
|
||||
AstAssign* const assignp
|
||||
= new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, operp};
|
||||
insertNextToStmt(nodep, assignp);
|
||||
// Immediately after incrementing - assign it to the original variable
|
||||
varp->addNextHere(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, operp});
|
||||
assignp->addNextHere(
|
||||
new AstAssign{fl, writep, new AstVarRef{fl, varp, VAccess::READ}});
|
||||
} else {
|
||||
// PostAdd/PostSub operations
|
||||
// assign the original variable to the temporary one
|
||||
varp->addNextHere(new AstAssign{fl, writep->cloneTree(true), operp});
|
||||
// Assign the original variable to the temporary one
|
||||
AstAssign* const assignp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
|
||||
readp->cloneTree(true)};
|
||||
insertNextToStmt(nodep, assignp);
|
||||
// Increment the original variable by one
|
||||
varp->addNextHere(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
|
||||
readp->cloneTree(true)});
|
||||
assignp->addNextHere(new AstAssign{fl, writep, operp});
|
||||
}
|
||||
|
||||
// Replace the node with the temporary
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ private:
|
|||
// Spec says value is integral, if negative is ignored
|
||||
AstVar* const varp
|
||||
= new AstVar{nodep->fileline(), VVarType::BLOCKTEMP, name, nodep->findSigned32DType()};
|
||||
varp->lifetime(VLifetime::AUTOMATIC);
|
||||
varp->usedLoopIdx(true);
|
||||
m_modp->addStmtsp(varp);
|
||||
AstNode* initsp = new AstAssign{
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ void V3LinkLevel::modSortByLevel() {
|
|||
ModVec tops; // Top level modules
|
||||
for (AstNodeModule* nodep = v3Global.rootp()->modulesp(); nodep;
|
||||
nodep = VN_AS(nodep->nextp(), NodeModule)) {
|
||||
if (nodep->level() <= 2) tops.push_back(nodep);
|
||||
if (nodep->level() <= 2 && !VN_IS(nodep, NotFoundModule)) tops.push_back(nodep);
|
||||
mods.push_back(nodep);
|
||||
}
|
||||
if (tops.size() >= 2) {
|
||||
|
|
@ -289,6 +289,8 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) {
|
|||
varp->trace(false);
|
||||
}
|
||||
|
||||
if (v3Global.opt.noTraceTop() && varp->isIO()) { varp->trace(false); }
|
||||
|
||||
AstPin* const pinp = new AstPin{
|
||||
oldvarp->fileline(), 0, varp->name(),
|
||||
new AstVarRef{varp->fileline(), varp,
|
||||
|
|
|
|||
|
|
@ -296,8 +296,16 @@ private:
|
|||
nodep->v3warn(STATICVAR, "Static variable with assignment declaration declared in a "
|
||||
"loop converted to automatic");
|
||||
}
|
||||
if (m_ftaskp && m_ftaskp->classMethod() && nodep->lifetime().isNone()) {
|
||||
nodep->lifetime(VLifetime::AUTOMATIC);
|
||||
if (m_ftaskp) {
|
||||
bool classMethod = m_ftaskp->classMethod();
|
||||
if (!classMethod) {
|
||||
AstClassOrPackageRef* const pkgrefp
|
||||
= VN_CAST(m_ftaskp->classOrPackagep(), ClassOrPackageRef);
|
||||
if (pkgrefp && VN_IS(pkgrefp->classOrPackagep(), Class)) classMethod = true;
|
||||
}
|
||||
if (classMethod && nodep->lifetime().isNone()) {
|
||||
nodep->lifetime(VLifetime::AUTOMATIC);
|
||||
}
|
||||
}
|
||||
if (nodep->lifetime().isNone() && nodep->varType() != VVarType::PORT) {
|
||||
nodep->lifetime(m_lifetime);
|
||||
|
|
@ -583,6 +591,17 @@ private:
|
|||
iterateChildren(nodep);
|
||||
}
|
||||
}
|
||||
void visit(AstWait* nodep) override {
|
||||
cleanFileline(nodep);
|
||||
iterateChildren(nodep);
|
||||
if (nodep->condp()->isZero()) {
|
||||
// Special case "wait(0)" we won't throw WAITCONST as user wrote
|
||||
// it that way with presumed intent - UVM does this.
|
||||
FileLine* const newfl = nodep->fileline();
|
||||
newfl->warnOff(V3ErrorCode::WAITCONST, true);
|
||||
nodep->fileline(newfl);
|
||||
}
|
||||
}
|
||||
void visit(AstWhile* nodep) override {
|
||||
cleanFileline(nodep);
|
||||
VL_RESTORER(m_insideLoop);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "V3Ast.h"
|
||||
#include "V3Global.h"
|
||||
#include "V3String.h"
|
||||
#include "V3Task.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
|
@ -45,7 +46,8 @@ class LinkResolveVisitor final : public VNVisitor {
|
|||
private:
|
||||
// NODE STATE
|
||||
// Entire netlist:
|
||||
// AstCaseItem::user2() // bool Moved default caseitems
|
||||
// AstCaseItem::user2() // bool Moved default caseitems
|
||||
// AstNodeFTaskRef::user2() // bool Processing - to check for recursion
|
||||
const VNUser2InUse m_inuser2;
|
||||
|
||||
// STATE
|
||||
|
|
@ -89,7 +91,9 @@ private:
|
|||
}
|
||||
}
|
||||
void visit(AstNodeCoverOrAssert* nodep) override {
|
||||
if (m_assertp) nodep->v3error("Assert not allowed under another assert");
|
||||
if (m_assertp) {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Assert not allowed under another assert");
|
||||
}
|
||||
m_assertp = nodep;
|
||||
iterateChildren(nodep);
|
||||
m_assertp = nullptr;
|
||||
|
|
@ -111,6 +115,7 @@ private:
|
|||
}
|
||||
|
||||
void visit(AstNodeFTask* nodep) override {
|
||||
// Note AstLet is handled specifically elsewhere
|
||||
// NodeTask: Remember its name for later resolution
|
||||
if (m_underGenerate) nodep->underGenerate(true);
|
||||
// Remember the existing symbol table scope
|
||||
|
|
@ -138,6 +143,47 @@ private:
|
|||
}
|
||||
void visit(AstNodeFTaskRef* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
if (AstLet* letp = VN_CAST(nodep->taskp(), Let)) {
|
||||
UINFO(7, "letSubstitute() " << nodep << " <- " << letp << endl);
|
||||
if (letp->user2()) {
|
||||
nodep->v3error("Recursive let substitution " << letp->prettyNameQ());
|
||||
nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
return;
|
||||
}
|
||||
letp->user2(true);
|
||||
// letp->dumpTree("-let-let ");
|
||||
// nodep->dumpTree("-let-ref ");
|
||||
AstStmtExpr* const letStmtp = VN_AS(letp->stmtsp(), StmtExpr);
|
||||
AstNodeExpr* const newp = letStmtp->exprp()->cloneTree(false);
|
||||
const V3TaskConnects tconnects = V3Task::taskConnects(nodep, letp->stmtsp());
|
||||
std::map<const AstVar*, AstNodeExpr*> portToExprs;
|
||||
for (const auto& tconnect : tconnects) {
|
||||
const AstVar* const portp = tconnect.first;
|
||||
const AstArg* const argp = tconnect.second;
|
||||
AstNodeExpr* const pinp = argp->exprp();
|
||||
if (!pinp) continue; // Argument error we'll find later
|
||||
portToExprs.emplace(portp, pinp);
|
||||
}
|
||||
// Replace VarRefs of arguments with the argument values
|
||||
newp->foreach([&](AstVarRef* refp) { //
|
||||
const auto it = portToExprs.find(refp->varp());
|
||||
if (it != portToExprs.end()) {
|
||||
AstNodeExpr* const pinp = it->second;
|
||||
UINFO(9, "let pin subst " << refp << " <- " << pinp << endl);
|
||||
// Side effects are copied into pins, to match other simulators
|
||||
refp->replaceWith(pinp->cloneTree(false));
|
||||
VL_DO_DANGLING(pushDeletep(refp), refp);
|
||||
}
|
||||
});
|
||||
// newp->dumpTree("-let-new ");
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
// Iterate to expand further now, so we can look for recursions
|
||||
visit(newp);
|
||||
letp->user2(false);
|
||||
return;
|
||||
}
|
||||
if (nodep->taskp() && (nodep->taskp()->dpiContext() || nodep->taskp()->dpiExport())) {
|
||||
nodep->scopeNamep(new AstScopeName{nodep->fileline(), false});
|
||||
}
|
||||
|
|
@ -168,6 +214,12 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void visit(AstLet* nodep) override {
|
||||
// Lets have been (or about to be) substituted, we can remove
|
||||
nodep->unlinkFrBack();
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
|
||||
void visit(AstPragma* nodep) override {
|
||||
if (nodep->pragType() == VPragmaType::HIER_BLOCK) {
|
||||
UASSERT_OBJ(m_modp, nodep, "HIER_BLOCK not under a module");
|
||||
|
|
|
|||
|
|
@ -620,6 +620,7 @@ private:
|
|||
// 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");
|
||||
return nullptr;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,12 +139,17 @@ private:
|
|||
T& m_mutexr;
|
||||
|
||||
public:
|
||||
/// Construct and hold given mutex lock until destruction or unlock()
|
||||
/// Lock given mutex and hold it for the object lifetime.
|
||||
explicit V3LockGuardImp(T& mutexr) VL_ACQUIRE(mutexr) VL_MT_SAFE
|
||||
: m_mutexr(mutexr) { // Need () or GCC 4.8 false warning
|
||||
mutexr.lock();
|
||||
}
|
||||
/// Destruct and unlock the mutex
|
||||
/// Take already locked mutex, and and hold the lock for the object lifetime.
|
||||
explicit V3LockGuardImp(T& mutexr, std::adopt_lock_t) VL_REQUIRES(mutexr) VL_MT_SAFE
|
||||
: m_mutexr(mutexr) { // Need () or GCC 4.8 false warning
|
||||
}
|
||||
|
||||
/// Unlock the mutex
|
||||
~V3LockGuardImp() VL_RELEASE() { m_mutexr.unlock(); }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ constexpr int MAX_SPRINTF_DOUBLE_SIZE
|
|||
//======================================================================
|
||||
// Errors
|
||||
|
||||
void V3Number::v3errorEnd(const std::ostringstream& str) const VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
void V3Number::v3errorEnd(const std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
|
||||
std::ostringstream nsstr;
|
||||
nsstr << str.str();
|
||||
if (m_nodep) {
|
||||
|
|
@ -84,12 +84,12 @@ void V3Number::v3errorEnd(const std::ostringstream& str) const VL_REQUIRES(V3Err
|
|||
} else if (m_fileline) {
|
||||
m_fileline->v3errorEnd(nsstr);
|
||||
} else {
|
||||
V3Error::s().v3errorEnd(nsstr);
|
||||
V3Error::v3errorEnd(nsstr);
|
||||
}
|
||||
}
|
||||
|
||||
void V3Number::v3errorEndFatal(const std::ostringstream& str) const
|
||||
VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
VL_RELEASE(V3Error::s().m_mutex) {
|
||||
v3errorEnd(str);
|
||||
assert(0); // LCOV_EXCL_LINE
|
||||
VL_UNREACHABLE;
|
||||
|
|
@ -511,6 +511,7 @@ string V3Number::ascii(bool prefixed, bool cleanVerilog) const VL_MT_STABLE {
|
|||
out << "%E-bad-width-double"; // LCOV_EXCL_LINE
|
||||
} else {
|
||||
out << toDouble();
|
||||
if (toDouble() == floor(toDouble())) out << ".0";
|
||||
}
|
||||
return out.str();
|
||||
} else if (isString()) {
|
||||
|
|
@ -2217,8 +2218,9 @@ V3Number& V3Number::opAssignNonXZ(const V3Number& lhs, bool ignoreXZ) {
|
|||
} else if (VL_UNLIKELY(lhs.isString())) {
|
||||
// Non-compatible types, see also opAToN()
|
||||
setZero();
|
||||
} else if (lhs.isDouble()) {
|
||||
setDouble(lhs.toDouble());
|
||||
} else {
|
||||
// Also handles double as is just bits
|
||||
for (int bit = 0; bit < this->width(); bit++) {
|
||||
setBit(bit, ignoreXZ ? lhs.bitIs1(bit) : lhs.bitIs(bit));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -566,9 +566,9 @@ private:
|
|||
}
|
||||
|
||||
public:
|
||||
void v3errorEnd(const std::ostringstream& sstr) const VL_REQUIRES(V3Error::s().m_mutex);
|
||||
void v3errorEnd(const std::ostringstream& sstr) const VL_RELEASE(V3Error::s().m_mutex);
|
||||
void v3errorEndFatal(const std::ostringstream& sstr) const VL_ATTR_NORETURN
|
||||
VL_REQUIRES(V3Error::s().m_mutex);
|
||||
VL_RELEASE(V3Error::s().m_mutex);
|
||||
void width(int width, bool sized = true) {
|
||||
m_data.m_sized = sized;
|
||||
m_data.resize(width);
|
||||
|
|
|
|||
|
|
@ -548,8 +548,8 @@ string V3Options::filePath(FileLine* fl, const string& modname, const string& la
|
|||
// Find a filename to read the specified module name,
|
||||
// using the incdir and libext's.
|
||||
// Return "" if not found.
|
||||
if (modname[0] == '/') {
|
||||
// If leading /, obey existing absolute path, so can find getStdPackagePath()
|
||||
if (!V3Os::filenameIsRel(modname)) {
|
||||
// modname is an absolute path, so can find getStdPackagePath()
|
||||
string exists = filePathCheckOneDir(modname, "");
|
||||
if (exists != "") return exists;
|
||||
}
|
||||
|
|
@ -806,6 +806,27 @@ void V3Options::notify() {
|
|||
cmdfl->v3error("--make cannot be used together with --build. Suggest see manual");
|
||||
}
|
||||
|
||||
// m_build, m_preprocOnly, m_dpiHdrOnly, m_lintOnly, and m_xmlOnly are mutually exclusive
|
||||
std::vector<std::string> backendFlags;
|
||||
if (m_build) {
|
||||
if (m_binary)
|
||||
backendFlags.push_back("--binary");
|
||||
else
|
||||
backendFlags.push_back("--build");
|
||||
}
|
||||
if (m_preprocOnly) backendFlags.push_back("-E");
|
||||
if (m_dpiHdrOnly) backendFlags.push_back("--dpi-hdr-only");
|
||||
if (m_lintOnly) backendFlags.push_back("--lint-only");
|
||||
if (m_xmlOnly) backendFlags.push_back("--xml-only");
|
||||
if (backendFlags.size() > 1) {
|
||||
std::string backendFlagsString = backendFlags.front();
|
||||
for (size_t i = 1; i < backendFlags.size(); i++) {
|
||||
backendFlagsString += ", " + backendFlags[i];
|
||||
}
|
||||
v3error("The following cannot be used together: " + backendFlagsString
|
||||
+ ". Suggest see manual");
|
||||
}
|
||||
|
||||
if (m_exe && !v3Global.opt.libCreate().empty()) {
|
||||
cmdfl->v3error("--exe cannot be used together with --lib-create. Suggest see manual");
|
||||
}
|
||||
|
|
@ -1081,6 +1102,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
|||
FileLine::globalWarnOff(V3ErrorCode::E_UNSUPPORTED, true);
|
||||
});
|
||||
DECL_OPTION("-binary", CbCall, [this]() {
|
||||
m_binary = true;
|
||||
m_build = true;
|
||||
m_exe = true;
|
||||
m_main = true;
|
||||
|
|
@ -1151,6 +1173,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
|||
DECL_OPTION("-debug-emitv", OnOff, &m_debugEmitV).undocumented();
|
||||
DECL_OPTION("-debug-exit-parse", OnOff, &m_debugExitParse).undocumented();
|
||||
DECL_OPTION("-debug-exit-uvm", OnOff, &m_debugExitUvm).undocumented();
|
||||
DECL_OPTION("-debug-exit-uvm23", OnOff, &m_debugExitUvm23).undocumented();
|
||||
DECL_OPTION("-debug-fatalsrc", CbCall, []() {
|
||||
v3fatalSrc("--debug-fatal-src");
|
||||
}).undocumented(); // See also --debug-abort
|
||||
|
|
@ -1496,6 +1519,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
|||
DECL_OPTION("-timing", OnOff, &m_timing);
|
||||
DECL_OPTION("-top-module", Set, &m_topModule);
|
||||
DECL_OPTION("-top", Set, &m_topModule);
|
||||
DECL_OPTION("-no-trace-top", Set, &m_noTraceTop);
|
||||
DECL_OPTION("-trace", OnOff, &m_trace);
|
||||
DECL_OPTION("-trace-coverage", OnOff, &m_traceCoverage);
|
||||
DECL_OPTION("-trace-depth", Set, &m_traceDepth);
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ private:
|
|||
bool m_autoflush = false; // main switch: --autoflush
|
||||
bool m_bboxSys = false; // main switch: --bbox-sys
|
||||
bool m_bboxUnsup = false; // main switch: --bbox-unsup
|
||||
bool m_binary = false; // main switch: --binary
|
||||
bool m_build = false; // main switch: --build
|
||||
bool m_cmake = false; // main switch: --make cmake
|
||||
bool m_context = true; // main switch: --Wcontext
|
||||
|
|
@ -235,6 +236,7 @@ private:
|
|||
bool m_debugEmitV = false; // main switch: --debug-emitv
|
||||
bool m_debugExitParse = false; // main switch: --debug-exit-parse
|
||||
bool m_debugExitUvm = false; // main switch: --debug-exit-uvm
|
||||
bool m_debugExitUvm23 = false; // main switch: --debug-exit-uvm23
|
||||
bool m_debugLeak = true; // main switch: --debug-leak
|
||||
bool m_debugNondeterminism = false; // main switch: --debug-nondeterminism
|
||||
bool m_debugPartition = false; // main switch: --debug-partition
|
||||
|
|
@ -280,6 +282,7 @@ private:
|
|||
bool m_traceCoverage = false; // main switch: --trace-coverage
|
||||
bool m_traceParams = true; // main switch: --trace-params
|
||||
bool m_traceStructs = false; // main switch: --trace-structs
|
||||
bool m_noTraceTop = false; // main switch: --no-trace-top
|
||||
bool m_traceUnderscore = false; // main switch: --trace-underscore
|
||||
bool m_underlineZero = false; // main switch: --underline-zero; undocumented old Verilator 2
|
||||
bool m_verilate = true; // main switch: --verilate
|
||||
|
|
@ -441,6 +444,7 @@ public:
|
|||
bool autoflush() const { return m_autoflush; }
|
||||
bool bboxSys() const { return m_bboxSys; }
|
||||
bool bboxUnsup() const { return m_bboxUnsup; }
|
||||
bool binary() const { return m_binary; }
|
||||
bool build() const { return m_build; }
|
||||
string buildDepBin() const { return m_buildDepBin; }
|
||||
void buildDepBin(const string& flag) { m_buildDepBin = flag; }
|
||||
|
|
@ -458,6 +462,7 @@ public:
|
|||
bool debugEmitV() const VL_MT_SAFE { return m_debugEmitV; }
|
||||
bool debugExitParse() const { return m_debugExitParse; }
|
||||
bool debugExitUvm() const { return m_debugExitUvm; }
|
||||
bool debugExitUvm23() const { return m_debugExitUvm23; }
|
||||
bool debugLeak() const { return m_debugLeak; }
|
||||
bool debugNondeterminism() const { return m_debugNondeterminism; }
|
||||
bool debugPartition() const { return m_debugPartition; }
|
||||
|
|
@ -578,6 +583,7 @@ public:
|
|||
bool protectKeyProvided() const { return !m_protectKey.empty(); }
|
||||
string protectKeyDefaulted() VL_MT_SAFE; // Set default key if not set by user
|
||||
string topModule() const { return m_topModule; }
|
||||
bool noTraceTop() const { return m_noTraceTop; }
|
||||
string unusedRegexp() const { return m_unusedRegexp; }
|
||||
string waiverOutput() const { return m_waiverOutput; }
|
||||
bool isWaiverOutput() const { return !m_waiverOutput.empty(); }
|
||||
|
|
|
|||
|
|
@ -597,7 +597,7 @@ public:
|
|||
|
||||
// For each logic vertex, make a T_MoveVertex, for each variable vertex, allocate storage
|
||||
for (V3GraphVertex* itp = m_graphp->verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (OrderLogicVertex* const lvtxp = dynamic_cast<OrderLogicVertex*>(itp)) {
|
||||
if (OrderLogicVertex* const lvtxp = itp->cast<OrderLogicVertex>()) {
|
||||
lvtxp->userp(m_vxMakerp->makeVertexp(lvtxp, nullptr, lvtxp->domainp()));
|
||||
} else {
|
||||
// This is an OrderVarVertex
|
||||
|
|
@ -607,7 +607,7 @@ public:
|
|||
}
|
||||
// Build edges between logic vertices
|
||||
for (V3GraphVertex* itp = m_graphp->verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (OrderLogicVertex* const lvtxp = dynamic_cast<OrderLogicVertex*>(itp)) {
|
||||
if (OrderLogicVertex* const lvtxp = itp->cast<OrderLogicVertex>()) {
|
||||
iterateLogicVertex(lvtxp);
|
||||
}
|
||||
}
|
||||
|
|
@ -941,8 +941,8 @@ void OrderMoveDomScope::movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp)
|
|||
|
||||
void OrderProcess::processDomains() {
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
OrderEitherVertex* const vertexp = dynamic_cast<OrderEitherVertex*>(itp);
|
||||
UASSERT(vertexp, "Null or vertex not derived from EitherVertex");
|
||||
UASSERT(itp, "Vertex should not be null");
|
||||
OrderEitherVertex* const vertexp = itp->as<OrderEitherVertex>();
|
||||
processDomainsIterate(vertexp);
|
||||
}
|
||||
}
|
||||
|
|
@ -958,7 +958,7 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) {
|
|||
|
||||
UINFO(5, " pdi: " << vertexp << endl);
|
||||
AstSenTree* domainp = nullptr;
|
||||
if (OrderLogicVertex* const lvtxp = dynamic_cast<OrderLogicVertex*>(vertexp)) {
|
||||
if (OrderLogicVertex* const lvtxp = vertexp->cast<OrderLogicVertex>()) {
|
||||
domainp = lvtxp->hybridp();
|
||||
}
|
||||
|
||||
|
|
@ -970,7 +970,7 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) {
|
|||
AstSenTree* fromDomainp = fromVertexp->domainp();
|
||||
UASSERT(!fromDomainp->hasCombo(), "There should be no need for combinational domains");
|
||||
|
||||
if (OrderVarVertex* const varVtxp = dynamic_cast<OrderVarVertex*>(fromVertexp)) {
|
||||
if (OrderVarVertex* const varVtxp = fromVertexp->cast<OrderVarVertex>()) {
|
||||
AstVarScope* const vscp = varVtxp->vscp();
|
||||
// Add in any external domains
|
||||
externalDomainps.clear();
|
||||
|
|
@ -1023,13 +1023,13 @@ void OrderProcess::processEdgeReport() {
|
|||
for (const auto& pair : m_trigToSen) trigToSen.emplace(*pair.first, pair.second);
|
||||
|
||||
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
|
||||
if (OrderVarVertex* const vvertexp = dynamic_cast<OrderVarVertex*>(itp)) {
|
||||
if (OrderVarVertex* const vvertexp = itp->cast<OrderVarVertex>()) {
|
||||
string name(vvertexp->vscp()->prettyName());
|
||||
if (dynamic_cast<OrderVarPreVertex*>(itp)) {
|
||||
if (itp->is<OrderVarPreVertex>()) {
|
||||
name += " {PRE}";
|
||||
} else if (dynamic_cast<OrderVarPostVertex*>(itp)) {
|
||||
} else if (itp->is<OrderVarPostVertex>()) {
|
||||
name += " {POST}";
|
||||
} else if (dynamic_cast<OrderVarPordVertex*>(itp)) {
|
||||
} else if (itp->is<OrderVarPordVertex>()) {
|
||||
name += " {PORD}";
|
||||
}
|
||||
std::ostringstream os;
|
||||
|
|
@ -1335,8 +1335,7 @@ void OrderProcess::processMTasks() {
|
|||
// information in V3EmitC when we lay out var's in memory.
|
||||
const OrderLogicVertex* const logicp = movep->logicp();
|
||||
for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
const OrderVarVertex* const pre_varp
|
||||
= dynamic_cast<const OrderVarVertex*>(edgep->fromp());
|
||||
const OrderVarVertex* const pre_varp = edgep->fromp()->cast<const OrderVarVertex>();
|
||||
if (!pre_varp) continue;
|
||||
AstVar* const varp = pre_varp->vscp()->varp();
|
||||
// varp depends on logicp, so logicp produces varp,
|
||||
|
|
@ -1344,8 +1343,7 @@ void OrderProcess::processMTasks() {
|
|||
varp->addProducingMTaskId(mtaskId);
|
||||
}
|
||||
for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
const OrderVarVertex* const post_varp
|
||||
= dynamic_cast<const OrderVarVertex*>(edgep->top());
|
||||
const OrderVarVertex* const post_varp = edgep->top()->cast<const OrderVarVertex>();
|
||||
if (!post_varp) continue;
|
||||
AstVar* const varp = post_varp->vscp()->varp();
|
||||
varp->addConsumingMTaskId(mtaskId);
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ public:
|
|||
// Vertex types
|
||||
|
||||
class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(OrderEitherVertex, V3GraphVertex)
|
||||
// Event domain of vertex. For OrderLogicVertex this represents the conditions when the logic
|
||||
// block must be executed. For OrderVarVertex, this is the union of the domains of all the
|
||||
// OrderLogicVertex vertices that drive the variable. If initially set to nullptr (e.g.: all
|
||||
|
|
@ -133,6 +134,7 @@ public:
|
|||
};
|
||||
|
||||
class OrderLogicVertex final : public OrderEitherVertex {
|
||||
VL_RTTI_IMPL(OrderLogicVertex, OrderEitherVertex)
|
||||
AstNode* const m_nodep; // The logic this vertex represents
|
||||
AstScope* const m_scopep; // Scope the logic is under
|
||||
AstSenTree* const m_hybridp; // Additional sensitivities for hybrid combinational logic
|
||||
|
|
@ -167,6 +169,7 @@ public:
|
|||
};
|
||||
|
||||
class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex {
|
||||
VL_RTTI_IMPL(OrderVarVertex, OrderEitherVertex)
|
||||
AstVarScope* const m_vscp;
|
||||
|
||||
public:
|
||||
|
|
@ -189,6 +192,7 @@ public:
|
|||
};
|
||||
|
||||
class OrderVarStdVertex final : public OrderVarVertex {
|
||||
VL_RTTI_IMPL(OrderVarStdVertex, OrderVarVertex)
|
||||
public:
|
||||
// CONSTRUCTOR
|
||||
OrderVarStdVertex(OrderGraph* graphp, AstVarScope* vscp)
|
||||
|
|
@ -205,6 +209,7 @@ public:
|
|||
};
|
||||
|
||||
class OrderVarPreVertex final : public OrderVarVertex {
|
||||
VL_RTTI_IMPL(OrderVarPreVertex, OrderVarVertex)
|
||||
public:
|
||||
// CONSTRUCTOR
|
||||
OrderVarPreVertex(OrderGraph* graphp, AstVarScope* vscp)
|
||||
|
|
@ -221,6 +226,7 @@ public:
|
|||
};
|
||||
|
||||
class OrderVarPostVertex final : public OrderVarVertex {
|
||||
VL_RTTI_IMPL(OrderVarPostVertex, OrderVarVertex)
|
||||
public:
|
||||
// CONSTRUCTOR
|
||||
OrderVarPostVertex(OrderGraph* graphp, AstVarScope* vscp)
|
||||
|
|
@ -237,6 +243,7 @@ public:
|
|||
};
|
||||
|
||||
class OrderVarPordVertex final : public OrderVarVertex {
|
||||
VL_RTTI_IMPL(OrderVarPordVertex, OrderVarVertex)
|
||||
public:
|
||||
// CONSTRUCTOR
|
||||
OrderVarPordVertex(OrderGraph* graphp, AstVarScope* vscp)
|
||||
|
|
@ -256,6 +263,7 @@ public:
|
|||
// Edge type
|
||||
|
||||
class OrderEdge final : public V3GraphEdge {
|
||||
VL_RTTI_IMPL(OrderEdge, V3GraphEdge)
|
||||
friend class OrderGraph; // Only the OrderGraph can create these
|
||||
// CONSTRUCTOR
|
||||
OrderEdge(OrderGraph* graphp, OrderEitherVertex* fromp, OrderEitherVertex* top, int weight,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
class OrderMoveDomScope;
|
||||
|
||||
class OrderMoveVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(OrderMoveVertex, V3GraphVertex)
|
||||
enum OrderMState : uint8_t { POM_WAIT, POM_READY, POM_MOVED };
|
||||
|
||||
OrderLogicVertex* const m_logicp;
|
||||
|
|
@ -92,6 +93,7 @@ public:
|
|||
|
||||
// Similar to OrderMoveVertex, but modified for threaded code generation.
|
||||
class MTaskMoveVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(MTaskMoveVertex, V3GraphVertex)
|
||||
// This could be more compact, since we know m_varp and m_logicp
|
||||
// cannot both be set. Each MTaskMoveVertex represents a logic node
|
||||
// or a var node, it can't be both.
|
||||
|
|
|
|||
48
src/V3Os.cpp
48
src/V3Os.cpp
|
|
@ -124,7 +124,9 @@ void V3Os::setenvStr(const string& envvar, const string& value, const string& wh
|
|||
//######################################################################
|
||||
// Generic filename utilities
|
||||
|
||||
string V3Os::filenameFromDirBase(const string& dir, const string& basename) {
|
||||
static bool isSlash(char ch) VL_PURE { return ch == '/' || ch == '\\'; }
|
||||
|
||||
string V3Os::filenameFromDirBase(const string& dir, const string& basename) VL_PURE {
|
||||
// Don't return ./{filename} because if filename was absolute, that makes it relative
|
||||
if (dir.empty() || dir == ".") {
|
||||
return basename;
|
||||
|
|
@ -133,22 +135,26 @@ string V3Os::filenameFromDirBase(const string& dir, const string& basename) {
|
|||
}
|
||||
}
|
||||
|
||||
string V3Os::filenameDir(const string& filename) {
|
||||
string::size_type pos;
|
||||
if ((pos = filename.rfind('/')) != string::npos) {
|
||||
return filename.substr(0, pos);
|
||||
} else {
|
||||
string V3Os::filenameDir(const string& filename) VL_PURE {
|
||||
// std::filesystem::path::parent_path
|
||||
auto it = filename.rbegin();
|
||||
for (; it != filename.rend(); ++it) {
|
||||
if (isSlash(*it)) break;
|
||||
}
|
||||
if (it.base() == filename.begin()) {
|
||||
return ".";
|
||||
} else {
|
||||
return {filename.begin(), (++it).base()};
|
||||
}
|
||||
}
|
||||
|
||||
string V3Os::filenameNonDir(const string& filename) VL_PURE {
|
||||
string::size_type pos;
|
||||
if ((pos = filename.rfind('/')) != string::npos) {
|
||||
return filename.substr(pos + 1);
|
||||
} else {
|
||||
return filename;
|
||||
// std::filesystem::path::filename
|
||||
auto it = filename.rbegin();
|
||||
for (; it != filename.rend(); ++it) {
|
||||
if (isSlash(*it)) break;
|
||||
}
|
||||
return string{it.base(), filename.end()};
|
||||
}
|
||||
|
||||
string V3Os::filenameNonExt(const string& filename) VL_PURE {
|
||||
|
|
@ -202,7 +208,7 @@ string V3Os::filenameSubstitute(const string& filename) {
|
|||
return result;
|
||||
}
|
||||
|
||||
string V3Os::filenameRealPath(const string& filename) {
|
||||
string V3Os::filenameRealPath(const string& filename) VL_PURE {
|
||||
// Get rid of all the ../ behavior in the middle of the paths.
|
||||
// If there is a ../ that goes down from the 'root' of this path it is preserved.
|
||||
char retpath[PATH_MAX];
|
||||
|
|
@ -219,8 +225,12 @@ string V3Os::filenameRealPath(const string& filename) {
|
|||
}
|
||||
}
|
||||
|
||||
bool V3Os::filenameIsRel(const string& filename) {
|
||||
bool V3Os::filenameIsRel(const string& filename) VL_PURE {
|
||||
#if defined(_MSC_VER)
|
||||
return std::filesystem::path(filename).is_relative();
|
||||
#else
|
||||
return (filename.length() > 0 && filename[0] != '/');
|
||||
#endif
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
|
|
@ -251,12 +261,14 @@ void V3Os::createDir(const string& dirname) {
|
|||
|
||||
void V3Os::unlinkRegexp(const string& dir, const string& regexp) {
|
||||
#ifdef _MSC_VER
|
||||
for (const auto& dirEntry : std::filesystem::directory_iterator(dir.c_str())) {
|
||||
if (VString::wildmatch(dirEntry.path().filename().string(), regexp.c_str())) {
|
||||
const string fullname = dir + "/" + dirEntry.path().filename().string();
|
||||
_unlink(fullname.c_str());
|
||||
try {
|
||||
for (const auto& dirEntry : std::filesystem::directory_iterator(dir.c_str())) {
|
||||
if (VString::wildmatch(dirEntry.path().filename().string(), regexp.c_str())) {
|
||||
const string fullname = dir + "/" + dirEntry.path().filename().string();
|
||||
_unlink(fullname.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::filesystem::filesystem_error const& ex) {}
|
||||
#else
|
||||
if (DIR* const dirp = opendir(dir.c_str())) {
|
||||
while (struct dirent* const direntp = readdir(dirp)) {
|
||||
|
|
|
|||
14
src/V3Os.h
14
src/V3Os.h
|
|
@ -35,20 +35,22 @@ public:
|
|||
static void setenvStr(const string& envvar, const string& value, const string& why);
|
||||
|
||||
// METHODS (generic filename utilities)
|
||||
static string filenameFromDirBase(const string& dir, const string& basename);
|
||||
/// Return non-directory part of filename
|
||||
static string filenameFromDirBase(const string& dir, const string& basename) VL_PURE;
|
||||
///< Return non-directory part of filename
|
||||
static string filenameNonDir(const string& filename) VL_PURE;
|
||||
/// Return non-extensioned (no .) part of filename
|
||||
///< Return non-extensioned (no .) part of filename
|
||||
static string filenameNonExt(const string& filename) VL_PURE;
|
||||
///< Return basename of filename
|
||||
static string filenameNonDirExt(const string& filename) VL_PURE {
|
||||
return filenameNonExt(filenameNonDir(filename));
|
||||
}
|
||||
static string filenameDir(const string& filename); ///< Return directory part of filename
|
||||
///< Return directory part of filename
|
||||
static string filenameDir(const string& filename) VL_PURE;
|
||||
/// Return filename with env vars removed
|
||||
static string filenameSubstitute(const string& filename);
|
||||
static string filenameRealPath(const string& filename); ///< Return realpath of filename
|
||||
static bool filenameIsRel(const string& filename); ///< True if relative
|
||||
///< Return realpath of filename
|
||||
static string filenameRealPath(const string& filename) VL_PURE;
|
||||
static bool filenameIsRel(const string& filename) VL_PURE; ///< True if relative
|
||||
|
||||
// METHODS (file utilities)
|
||||
static string getline(std::istream& is, char delim = '\n');
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
// For each cell:
|
||||
// If parameterized,
|
||||
// Determine all parameter widths, constant values.
|
||||
// (Interfaces also matter, as if an interface is parameterized
|
||||
// (Interfaces also matter, as if a module is parameterized
|
||||
// this effectively changes the width behavior of all that
|
||||
// reference the iface.)
|
||||
// Clone module cell calls, renaming with __{par1}_{par2}_...
|
||||
|
|
@ -227,6 +227,10 @@ class ParamProcessor final : public VNDeleter {
|
|||
// // (0=not processed, 1=iterated, but no number,
|
||||
// // 65+ parameter numbered)
|
||||
// NODE STATE - Shared with ParamVisitor
|
||||
// AstClass::user4p() // AstClass* Unchanged copy of the parameterized class node.
|
||||
// The class node may be modified according to parameter
|
||||
// values and an unchanged copy is needed to instantiate
|
||||
// classes with different parameters.
|
||||
// AstNodeModule::user5() // bool True if processed
|
||||
// AstGenFor::user5() // bool True if processed
|
||||
// AstVar::user5() // bool True if constant propagated
|
||||
|
|
@ -574,8 +578,8 @@ class ParamProcessor final : public VNDeleter {
|
|||
// Note all module internal variables will be re-linked to the new modules by clone
|
||||
// However links outside the module (like on the upper cells) will not.
|
||||
AstNodeModule* newmodp;
|
||||
if (srcModp->user2p()) {
|
||||
newmodp = VN_CAST(srcModp->user2p()->cloneTree(false), NodeModule);
|
||||
if (srcModp->user4p()) {
|
||||
newmodp = VN_CAST(srcModp->user4p()->cloneTree(false), NodeModule);
|
||||
} else {
|
||||
newmodp = srcModp->cloneTree(false);
|
||||
}
|
||||
|
|
@ -617,7 +621,7 @@ class ParamProcessor final : public VNDeleter {
|
|||
// Grab all I/O so we can remap our pins later
|
||||
// Note we allow multiple users of a parameterized model,
|
||||
// thus we need to stash this info.
|
||||
collectPins(clonemapp, newmodp, srcModp->user2p());
|
||||
collectPins(clonemapp, newmodp, srcModp->user4p());
|
||||
// Relink parameter vars to the new module
|
||||
relinkPins(clonemapp, paramsp);
|
||||
// Fix any interface references
|
||||
|
|
@ -856,14 +860,14 @@ class ParamProcessor final : public VNDeleter {
|
|||
|
||||
if (!any_overrides) {
|
||||
UINFO(8, "Cell parameters all match original values, skipping expansion.\n");
|
||||
// If it's the first use of the default instance, create a copy and store it in user2p.
|
||||
// user2p will also be used to check if the default instance is used.
|
||||
if (!srcModpr->user2p() && VN_IS(srcModpr, Class)) {
|
||||
// If it's the first use of the default instance, create a copy and store it in user4p.
|
||||
// user4p will also be used to check if the default instance is used.
|
||||
if (!srcModpr->user4p() && VN_IS(srcModpr, Class)) {
|
||||
AstClass* classCopyp = VN_AS(srcModpr, Class)->cloneTree(false);
|
||||
// It is a temporary copy of the original class node, stored in order to create
|
||||
// another instances. It is needed only during class instantiation.
|
||||
pushDeletep(classCopyp);
|
||||
srcModpr->user2p(classCopyp);
|
||||
srcModpr->user4p(classCopyp);
|
||||
storeOriginalParams(classCopyp);
|
||||
}
|
||||
} else if (AstNodeModule* const paramedModp
|
||||
|
|
@ -972,11 +976,7 @@ public:
|
|||
|
||||
class ParamVisitor final : public VNVisitor {
|
||||
// NODE STATE
|
||||
// AstNodeModule::user1 -> bool: already fixed level
|
||||
// AstClass::user2p -> AstClass*: Unchanged copy of the parameterized class node.
|
||||
// The class node may be modified according to parameter
|
||||
// values and an unchanged copy is needed to instantiate
|
||||
// classes with different parameters.
|
||||
// AstNodeModule::user1 -> bool: already fixed level (temporary)
|
||||
|
||||
// STATE
|
||||
ParamProcessor m_processor; // De-parameterize a cell, build modules
|
||||
|
|
@ -1435,7 +1435,7 @@ public:
|
|||
for (AstNodeModule* const modp : modps) netlistp->addModulesp(modp);
|
||||
|
||||
for (AstClass* const classp : m_paramClasses) {
|
||||
if (!classp->user2p()) {
|
||||
if (!classp->user4p()) {
|
||||
// The default value isn't referenced, so it can be removed
|
||||
VL_DO_DANGLING(pushDeletep(classp->unlinkFrBack()), classp);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -448,10 +448,10 @@ private:
|
|||
|
||||
public:
|
||||
// METHODS
|
||||
SiblingMC* toSiblingMC(); // Instead of dynamic_cast
|
||||
const SiblingMC* toSiblingMC() const; // Instead of dynamic_cast
|
||||
MTaskEdge* toMTaskEdge(); // Instead of dynamic_cast
|
||||
const MTaskEdge* toMTaskEdge() const; // Instead of dynamic_cast
|
||||
SiblingMC* toSiblingMC(); // Instead of cast<>/as<>
|
||||
const SiblingMC* toSiblingMC() const; // Instead of cast<>/as<>
|
||||
MTaskEdge* toMTaskEdge(); // Instead of cast<>/as<>
|
||||
const MTaskEdge* toMTaskEdge() const; // Instead of cast<>/as<>
|
||||
bool mergeWouldCreateCycle() const; // Instead of virtual method
|
||||
|
||||
inline void rescore();
|
||||
|
|
@ -511,6 +511,7 @@ static_assert(!std::is_polymorphic<SiblingMC>::value, "Should not have a vtable"
|
|||
|
||||
// GraphEdge for the MTask graph
|
||||
class MTaskEdge final : public V3GraphEdge, public MergeCandidate {
|
||||
VL_RTTI_IMPL(MTaskEdge, V3GraphEdge)
|
||||
friend class LogicMTask;
|
||||
template <GraphWay::en T_Way>
|
||||
friend class PartPropagateCp;
|
||||
|
|
@ -806,7 +807,7 @@ public:
|
|||
UINFO(0, " Parallelism factor = " << parallelismFactor() << endl);
|
||||
}
|
||||
static uint32_t vertexCost(const V3GraphVertex* vertexp) {
|
||||
return dynamic_cast<const AbstractMTask*>(vertexp)->cost();
|
||||
return vertexp->as<const AbstractMTask>()->cost();
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -857,7 +858,7 @@ static void partInitCriticalPaths(V3Graph* mtasksp) {
|
|||
// They would have been all zeroes on initial creation of the MTaskEdges.
|
||||
for (V3GraphVertex* vxp = mtasksp->verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
for (V3GraphEdge* edgep = vxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
MTaskEdge* const mtedgep = dynamic_cast<MTaskEdge*>(edgep);
|
||||
MTaskEdge* const mtedgep = edgep->as<MTaskEdge>();
|
||||
mtedgep->resetCriticalPaths();
|
||||
}
|
||||
}
|
||||
|
|
@ -1839,8 +1840,8 @@ private:
|
|||
if (!m_tracingCall) return;
|
||||
m_tracingCall = false;
|
||||
if (nodep->dpiImportWrapper()) {
|
||||
if (nodep->pure() ? !v3Global.opt.threadsDpiPure()
|
||||
: !v3Global.opt.threadsDpiUnpure()) {
|
||||
if (nodep->dpiPure() ? !v3Global.opt.threadsDpiPure()
|
||||
: !v3Global.opt.threadsDpiUnpure()) {
|
||||
m_hasDpiHazard = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1966,14 +1967,14 @@ private:
|
|||
void findAdjacentTasks(const OrderVarStdVertex* varVtxp, TasksByRank& tasksByRank) {
|
||||
// Find all writer tasks for this variable, group by rank.
|
||||
for (V3GraphEdge* edgep = varVtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
if (const auto* const logicVtxp = dynamic_cast<OrderLogicVertex*>(edgep->fromp())) {
|
||||
if (const auto* const logicVtxp = edgep->fromp()->cast<OrderLogicVertex>()) {
|
||||
LogicMTask* const writerMtaskp = static_cast<LogicMTask*>(logicVtxp->userp());
|
||||
tasksByRank[writerMtaskp->rank()].insert(writerMtaskp);
|
||||
}
|
||||
}
|
||||
// Not: Find all reader tasks for this variable, group by rank.
|
||||
// There was "broken" code here to find readers, but fixing it to
|
||||
// work properly harmed performance on some tests, see #3360.
|
||||
// work properly harmed performance on some tests, see issue #3360.
|
||||
}
|
||||
void mergeSameRankTasks(const TasksByRank& tasksByRank) {
|
||||
LogicMTask* lastRecipientp = nullptr;
|
||||
|
|
@ -2058,7 +2059,7 @@ public:
|
|||
nextp = vtxp->verticesNextp();
|
||||
// Only consider OrderVarStdVertex which reflects
|
||||
// an actual lvalue assignment; the others do not.
|
||||
if (const OrderVarStdVertex* const vvtxp = dynamic_cast<OrderVarStdVertex*>(vtxp)) {
|
||||
if (const OrderVarStdVertex* const vvtxp = vtxp->cast<OrderVarStdVertex>()) {
|
||||
if (vvtxp->vscp()->varp()->isSc()) {
|
||||
systemCVars.push_back(vvtxp);
|
||||
} else {
|
||||
|
|
@ -2203,7 +2204,7 @@ public:
|
|||
const uint32_t thisThreadId = threadId(mtaskp);
|
||||
uint32_t result = 0;
|
||||
for (V3GraphEdge* edgep = mtaskp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
const ExecMTask* const prevp = dynamic_cast<ExecMTask*>(edgep->fromp());
|
||||
const ExecMTask* const prevp = edgep->fromp()->as<ExecMTask>();
|
||||
if (threadId(prevp) != thisThreadId) ++result;
|
||||
}
|
||||
return result;
|
||||
|
|
@ -2248,7 +2249,7 @@ void ThreadSchedule::dumpDotFile(const V3Graph& graph, const string& filename) c
|
|||
// Find minimum cost MTask for scaling MTask node widths
|
||||
uint32_t minCost = UINT32_MAX;
|
||||
for (const V3GraphVertex* vxp = graph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
if (const ExecMTask* const mtaskp = dynamic_cast<const ExecMTask*>(vxp)) {
|
||||
if (const ExecMTask* const mtaskp = vxp->cast<const ExecMTask>()) {
|
||||
minCost = minCost > mtaskp->cost() ? mtaskp->cost() : minCost;
|
||||
}
|
||||
}
|
||||
|
|
@ -2271,13 +2272,13 @@ void ThreadSchedule::dumpDotFile(const V3Graph& graph, const string& filename) c
|
|||
|
||||
// Emit MTasks
|
||||
for (const V3GraphVertex* vxp = graph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
if (const ExecMTask* const mtaskp = dynamic_cast<const ExecMTask*>(vxp)) emitMTask(mtaskp);
|
||||
if (const ExecMTask* const mtaskp = vxp->cast<const ExecMTask>()) emitMTask(mtaskp);
|
||||
}
|
||||
|
||||
// Emit MTask dependency edges
|
||||
*logp << "\n // MTask dependencies\n";
|
||||
for (const V3GraphVertex* vxp = graph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
if (const ExecMTask* const mtaskp = dynamic_cast<const ExecMTask*>(vxp)) {
|
||||
if (const ExecMTask* const mtaskp = vxp->cast<const ExecMTask>()) {
|
||||
for (V3GraphEdge* edgep = mtaskp->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
const V3GraphVertex* const top = edgep->top();
|
||||
*logp << " " << vxp->name() << " -> " << top->name() << "\n";
|
||||
|
|
@ -2365,7 +2366,7 @@ private:
|
|||
|
||||
bool isReady(ThreadSchedule& schedule, const ExecMTask* mtaskp) {
|
||||
for (V3GraphEdge* edgeInp = mtaskp->inBeginp(); edgeInp; edgeInp = edgeInp->inNextp()) {
|
||||
const ExecMTask* const prevp = dynamic_cast<ExecMTask*>(edgeInp->fromp());
|
||||
const ExecMTask* const prevp = edgeInp->fromp()->as<const ExecMTask>();
|
||||
if (schedule.threadId(prevp) == ThreadSchedule::UNASSIGNED) {
|
||||
// This predecessor is not assigned yet
|
||||
return false;
|
||||
|
|
@ -2388,7 +2389,7 @@ public:
|
|||
|
||||
// Build initial ready list
|
||||
for (V3GraphVertex* vxp = mtaskGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
ExecMTask* const mtaskp = dynamic_cast<ExecMTask*>(vxp);
|
||||
ExecMTask* const mtaskp = vxp->as<ExecMTask>();
|
||||
if (isReady(schedule, mtaskp)) readyMTasks.insert(mtaskp);
|
||||
}
|
||||
|
||||
|
|
@ -2409,7 +2410,7 @@ public:
|
|||
}
|
||||
for (V3GraphEdge* edgep = mtaskp->inBeginp(); edgep;
|
||||
edgep = edgep->inNextp()) {
|
||||
const ExecMTask* const priorp = dynamic_cast<ExecMTask*>(edgep->fromp());
|
||||
const ExecMTask* const priorp = edgep->fromp()->as<ExecMTask>();
|
||||
const uint32_t priorEndTime = completionTime(schedule, priorp, threadId);
|
||||
if (priorEndTime > timeBegin) timeBegin = priorEndTime;
|
||||
}
|
||||
|
|
@ -2449,7 +2450,7 @@ public:
|
|||
UASSERT_OBJ(erased > 0, bestMtaskp, "Should have erased something?");
|
||||
for (V3GraphEdge* edgeOutp = bestMtaskp->outBeginp(); edgeOutp;
|
||||
edgeOutp = edgeOutp->outNextp()) {
|
||||
ExecMTask* const nextp = dynamic_cast<ExecMTask*>(edgeOutp->top());
|
||||
ExecMTask* const nextp = edgeOutp->top()->as<ExecMTask>();
|
||||
// Dependent MTask should not yet be assigned to a thread
|
||||
UASSERT(schedule.threadId(nextp) == ThreadSchedule::UNASSIGNED,
|
||||
"Tasks after one being assigned should not be assigned yet");
|
||||
|
|
@ -2540,7 +2541,7 @@ void V3Partition::debugMTaskGraphStats(const V3Graph* graphp, const string& stag
|
|||
for (const V3GraphVertex* mtaskp = graphp->verticesBeginp(); mtaskp;
|
||||
mtaskp = mtaskp->verticesNextp()) {
|
||||
++mtaskCount;
|
||||
uint32_t mtaskCost = dynamic_cast<const AbstractMTask*>(mtaskp)->cost();
|
||||
uint32_t mtaskCost = mtaskp->as<const AbstractMTask>()->cost();
|
||||
totalCost += mtaskCost;
|
||||
|
||||
unsigned log2Cost = 0;
|
||||
|
|
@ -2928,7 +2929,7 @@ static void fillinCosts(V3Graph* execMTaskGraphp) {
|
|||
|
||||
for (const V3GraphVertex* vxp = execMTaskGraphp->verticesBeginp(); vxp;
|
||||
vxp = vxp->verticesNextp()) {
|
||||
ExecMTask* const mtp = dynamic_cast<ExecMTask*>(const_cast<V3GraphVertex*>(vxp));
|
||||
ExecMTask* const mtp = const_cast<V3GraphVertex*>(vxp)->as<ExecMTask>();
|
||||
// Compute name of mtask, for hash lookup
|
||||
mtp->hashName(m_uniqueNames.get(mtp->bodyp()));
|
||||
|
||||
|
|
@ -2949,7 +2950,7 @@ static void fillinCosts(V3Graph* execMTaskGraphp) {
|
|||
int missingProfiles = 0;
|
||||
for (const V3GraphVertex* vxp = execMTaskGraphp->verticesBeginp(); vxp;
|
||||
vxp = vxp->verticesNextp()) {
|
||||
ExecMTask* const mtp = dynamic_cast<ExecMTask*>(const_cast<V3GraphVertex*>(vxp));
|
||||
ExecMTask* const mtp = const_cast<V3GraphVertex*>(vxp)->as<ExecMTask>();
|
||||
const uint32_t costEstimate = costs[mtp->id()].first;
|
||||
const uint64_t costProfiled = costs[mtp->id()].second;
|
||||
UINFO(9, "ce = " << costEstimate << " cp=" << costProfiled << endl);
|
||||
|
|
@ -2978,14 +2979,14 @@ static void fillinCosts(V3Graph* execMTaskGraphp) {
|
|||
static void finalizeCosts(V3Graph* execMTaskGraphp) {
|
||||
GraphStreamUnordered ser(execMTaskGraphp, GraphWay::REVERSE);
|
||||
while (const V3GraphVertex* const vxp = ser.nextp()) {
|
||||
ExecMTask* const mtp = dynamic_cast<ExecMTask*>(const_cast<V3GraphVertex*>(vxp));
|
||||
ExecMTask* const mtp = const_cast<V3GraphVertex*>(vxp)->as<ExecMTask>();
|
||||
// "Priority" is the critical path from the start of the mtask, to
|
||||
// the end of the graph reachable from this mtask. Given the
|
||||
// choice among several ready mtasks, we'll want to start the
|
||||
// highest priority one first, so we're always working on the "long
|
||||
// pole"
|
||||
for (V3GraphEdge* edgep = mtp->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
const ExecMTask* const followp = dynamic_cast<ExecMTask*>(edgep->top());
|
||||
const ExecMTask* const followp = edgep->top()->as<ExecMTask>();
|
||||
if ((followp->priority() + mtp->cost()) > mtp->priority()) {
|
||||
mtp->priority(followp->priority() + mtp->cost());
|
||||
}
|
||||
|
|
@ -2996,7 +2997,7 @@ static void finalizeCosts(V3Graph* execMTaskGraphp) {
|
|||
// (It's common for tasks to shrink to nothing when V3LifePost
|
||||
// removes dly assignments.)
|
||||
for (V3GraphVertex* vxp = execMTaskGraphp->verticesBeginp(); vxp;) {
|
||||
ExecMTask* const mtp = dynamic_cast<ExecMTask*>(vxp);
|
||||
ExecMTask* const mtp = vxp->as<ExecMTask>();
|
||||
vxp = vxp->verticesNextp(); // Advance before delete
|
||||
|
||||
// Don't rely on checking mtp->cost() == 0 to detect an empty task.
|
||||
|
|
@ -3100,7 +3101,7 @@ static void addMTaskToFunction(const ThreadSchedule& schedule, const uint32_t th
|
|||
|
||||
// For any dependent mtask that's on another thread, signal one dependency completion.
|
||||
for (V3GraphEdge* edgep = mtaskp->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
const ExecMTask* const nextp = dynamic_cast<ExecMTask*>(edgep->top());
|
||||
const ExecMTask* const nextp = edgep->top()->as<ExecMTask>();
|
||||
if (schedule.threadId(nextp) != threadId) {
|
||||
addStrStmt("vlSelf->__Vm_mtaskstate_" + cvtToStr(nextp->id())
|
||||
+ ".signalUpstreamDone(even_cycle);\n");
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
// MTasks and graph structures
|
||||
|
||||
class AbstractMTask VL_NOT_FINAL : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(AbstractMTask, V3GraphVertex)
|
||||
public:
|
||||
explicit AbstractMTask(V3Graph* graphp)
|
||||
: V3GraphVertex{graphp} {}
|
||||
|
|
@ -38,6 +39,7 @@ public:
|
|||
};
|
||||
|
||||
class AbstractLogicMTask VL_NOT_FINAL : public AbstractMTask {
|
||||
VL_RTTI_IMPL(AbstractLogicMTask, AbstractMTask)
|
||||
public:
|
||||
// TYPES
|
||||
using VxList = std::list<MTaskMoveVertex*>;
|
||||
|
|
@ -53,6 +55,7 @@ public:
|
|||
};
|
||||
|
||||
class ExecMTask final : public AbstractMTask {
|
||||
VL_RTTI_IMPL(ExecMTask, AbstractMTask)
|
||||
private:
|
||||
AstMTaskBody* const m_bodyp; // Task body
|
||||
const uint32_t m_id; // Unique id of this mtask.
|
||||
|
|
|
|||
|
|
@ -501,11 +501,11 @@ size_t V3PreLex::inputToLex(char* buf, size_t max_size) {
|
|||
// become a stale invalid pointer.
|
||||
//
|
||||
VPreStream* streamp = curStreamp();
|
||||
if (debug() >= 10) {
|
||||
if (debug() >= 10) { // LCOV_EXCL_START
|
||||
cout << "- pp:inputToLex ITL s=" << max_size << " bs=" << streamp->m_buffers.size()
|
||||
<< endl;
|
||||
dumpStack();
|
||||
}
|
||||
} // LCOV_EXCL_STOP
|
||||
// For testing, use really small chunks
|
||||
// if (max_size > 13) max_size=13;
|
||||
again:
|
||||
|
|
@ -696,16 +696,16 @@ void V3PreLex::warnBackslashSpace() {
|
|||
BSSPACE, "Backslash followed by whitespace, perhaps the whitespace is accidental?");
|
||||
}
|
||||
|
||||
void V3PreLex::dumpSummary() {
|
||||
void V3PreLex::dumpSummary() { // LCOV_EXCL_START
|
||||
cout << "- pp::dumpSummary curBuf=" << cvtToHex(currentBuffer());
|
||||
#ifdef FLEX_DEBUG // Else peeking at internals may cause portability issues
|
||||
ssize_t left = (yy_n_chars - (yy_c_buf_p - currentBuffer()->yy_ch_buf));
|
||||
cout << " left=" << std::dec << left;
|
||||
#endif
|
||||
cout << endl;
|
||||
}
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
void V3PreLex::dumpStack() {
|
||||
void V3PreLex::dumpStack() { // LCOV_EXCL_START
|
||||
// For debug use
|
||||
dumpSummary();
|
||||
std::stack<VPreStream*> tmpstack = LEXP->m_streampStack;
|
||||
|
|
@ -717,7 +717,7 @@ void V3PreLex::dumpStack() {
|
|||
<< (streamp->m_eof ? " [EOF]" : "") << (streamp->m_file ? " [FILE]" : "") << endl;
|
||||
tmpstack.pop();
|
||||
}
|
||||
}
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
string V3PreLex::cleanDbgStrg(const string& in) {
|
||||
string result = in;
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ public:
|
|||
// For getline()
|
||||
string m_lineChars; ///< Characters left for next line
|
||||
|
||||
void v3errorEnd(std::ostringstream& str) VL_REQUIRES(V3Error::s().m_mutex) {
|
||||
void v3errorEnd(std::ostringstream& str) VL_RELEASE(V3Error::s().m_mutex) {
|
||||
fileline()->v3errorEnd(str);
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ private:
|
|||
|
||||
void parsingOn() {
|
||||
m_off--;
|
||||
if (m_off < 0) fatalSrc("Underflow of parsing cmds");
|
||||
if (m_off < 0) v3fatalSrc("Underflow of parsing cmds");
|
||||
// addLineComment no longer needed; getFinalToken will correct.
|
||||
}
|
||||
void parsingOff() { m_off++; }
|
||||
|
|
@ -541,7 +541,7 @@ void V3PreProcImp::unputString(const string& strg) {
|
|||
// so instead we scan from a temporary buffer, then on EOF return.
|
||||
// This is also faster than the old scheme, amazingly.
|
||||
if (VL_UNCOVERABLE(m_lexp->m_bufferState != m_lexp->currentBuffer())) {
|
||||
fatalSrc("bufferStack missing current buffer; will return incorrectly");
|
||||
v3fatalSrc("bufferStack missing current buffer; will return incorrectly");
|
||||
// Hard to debug lost text as won't know till much later
|
||||
}
|
||||
m_lexp->scanBytes(strg);
|
||||
|
|
@ -1091,7 +1091,7 @@ int V3PreProcImp::getStateToken() {
|
|||
m_lexp->pushStateDefForm();
|
||||
goto next_tok;
|
||||
} else { // LCOV_EXCL_LINE
|
||||
fatalSrc("Bad case\n");
|
||||
v3fatalSrc("Bad case\n");
|
||||
}
|
||||
goto next_tok;
|
||||
} else if (tok == VP_TEXT) {
|
||||
|
|
@ -1165,7 +1165,7 @@ int V3PreProcImp::getStateToken() {
|
|||
} else {
|
||||
const string msg
|
||||
= std::string{"Bad define text, unexpected "} + tokenName(tok) + "\n";
|
||||
fatalSrc(msg);
|
||||
v3fatalSrc(msg);
|
||||
}
|
||||
statePop();
|
||||
// DEFVALUE is terminated by a return, but lex can't return both tokens.
|
||||
|
|
@ -1179,7 +1179,7 @@ int V3PreProcImp::getStateToken() {
|
|||
goto next_tok;
|
||||
} else {
|
||||
if (VL_UNCOVERABLE(m_defRefs.empty())) {
|
||||
fatalSrc("Shouldn't be in DEFPAREN w/o active defref");
|
||||
v3fatalSrc("Shouldn't be in DEFPAREN w/o active defref");
|
||||
}
|
||||
const VDefineRef* const refp = &(m_defRefs.top());
|
||||
error(std::string{"Expecting ( to begin argument list for define reference `"}
|
||||
|
|
@ -1190,7 +1190,7 @@ int V3PreProcImp::getStateToken() {
|
|||
}
|
||||
case ps_DEFARG: {
|
||||
if (VL_UNCOVERABLE(m_defRefs.empty())) {
|
||||
fatalSrc("Shouldn't be in DEFARG w/o active defref");
|
||||
v3fatalSrc("Shouldn't be in DEFARG w/o active defref");
|
||||
}
|
||||
VDefineRef* refp = &(m_defRefs.top());
|
||||
refp->nextarg(refp->nextarg() + m_lexp->m_defValue);
|
||||
|
|
@ -1217,7 +1217,7 @@ int V3PreProcImp::getStateToken() {
|
|||
if (state()
|
||||
== ps_JOIN) { // Handle {left}```FOO(ARG) where `FOO(ARG) might be empty
|
||||
if (VL_UNCOVERABLE(m_joinStack.empty())) {
|
||||
fatalSrc("`` join stack empty, but in a ``");
|
||||
v3fatalSrc("`` join stack empty, but in a ``");
|
||||
}
|
||||
const string lhs = m_joinStack.top();
|
||||
m_joinStack.pop();
|
||||
|
|
@ -1307,7 +1307,7 @@ int V3PreProcImp::getStateToken() {
|
|||
case ps_JOIN: {
|
||||
if (tok == VP_SYMBOL || tok == VP_TEXT) {
|
||||
if (VL_UNCOVERABLE(m_joinStack.empty())) {
|
||||
fatalSrc("`` join stack empty, but in a ``");
|
||||
v3fatalSrc("`` join stack empty, but in a ``");
|
||||
}
|
||||
const string lhs = m_joinStack.top();
|
||||
m_joinStack.pop();
|
||||
|
|
@ -1363,7 +1363,7 @@ int V3PreProcImp::getStateToken() {
|
|||
goto next_tok;
|
||||
}
|
||||
}
|
||||
default: fatalSrc("Bad case\n");
|
||||
default: v3fatalSrc("Bad case\n");
|
||||
}
|
||||
// Default is to do top level expansion of some tokens
|
||||
switch (tok) {
|
||||
|
|
@ -1445,7 +1445,7 @@ int V3PreProcImp::getStateToken() {
|
|||
// Just output the substitution
|
||||
if (state() == ps_JOIN) { // Handle {left}```FOO where `FOO might be empty
|
||||
if (VL_UNCOVERABLE(m_joinStack.empty())) {
|
||||
fatalSrc("`` join stack empty, but in a ``");
|
||||
v3fatalSrc("`` join stack empty, but in a ``");
|
||||
}
|
||||
const string lhs = m_joinStack.top();
|
||||
m_joinStack.pop();
|
||||
|
|
@ -1481,7 +1481,7 @@ int V3PreProcImp::getStateToken() {
|
|||
goto next_tok;
|
||||
}
|
||||
}
|
||||
fatalSrc("Bad case\n"); // FALLTHRU
|
||||
v3fatalSrc("Bad case\n"); // FALLTHRU
|
||||
goto next_tok; // above fatal means unreachable, but fixes static analysis warning
|
||||
}
|
||||
case VP_ERROR: {
|
||||
|
|
@ -1518,7 +1518,7 @@ int V3PreProcImp::getStateToken() {
|
|||
case VP_DEFFORM: // Handled by state=ps_DEFFORM;
|
||||
case VP_DEFVALUE: // Handled by state=ps_DEFVALUE;
|
||||
default: // LCOV_EXCL_LINE
|
||||
fatalSrc(std::string{"Internal error: Unexpected token "} + tokenName(tok) + "\n");
|
||||
v3fatalSrc(std::string{"Internal error: Unexpected token "} + tokenName(tok) + "\n");
|
||||
break; // LCOV_EXCL_LINE
|
||||
}
|
||||
return tok;
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@
|
|||
#include <list>
|
||||
#include <map>
|
||||
|
||||
// Compatibility with Verilog-Perl's preprocessor
|
||||
#define fatalSrc(msg) v3fatalSrc(msg)
|
||||
|
||||
class VInFilter;
|
||||
class VSpellCheck;
|
||||
|
||||
|
|
|
|||
|
|
@ -54,11 +54,11 @@ private:
|
|||
const VNUser2InUse m_inuser2;
|
||||
|
||||
// STATE - across all visitors
|
||||
V3UniqueNames m_tempNames; // For generating unique temporary variable names
|
||||
VDouble0 m_extractedToConstPool; // Statistic tracking
|
||||
|
||||
// STATE - for current visit position (use VL_RESTORER)
|
||||
AstCFunc* m_cfuncp = nullptr; // Current block
|
||||
int m_tmpVarCnt = 0; // Number of temporary variables created inside a function
|
||||
AstNode* m_stmtp = nullptr; // Current statement
|
||||
AstCCall* m_callp = nullptr; // Current AstCCall
|
||||
AstWhile* m_inWhilep = nullptr; // Inside while loop, special statement additions
|
||||
|
|
@ -138,7 +138,8 @@ private:
|
|||
++m_extractedToConstPool;
|
||||
} else {
|
||||
// Keep as local temporary. Name based on hash of node for output stability.
|
||||
varp = new AstVar{fl, VVarType::STMTTEMP, m_tempNames.get(nodep), nodep->dtypep()};
|
||||
varp = new AstVar{fl, VVarType::STMTTEMP, "__Vtemp_" + cvtToStr(++m_tmpVarCnt),
|
||||
nodep->dtypep()};
|
||||
m_cfuncp->addInitsp(varp);
|
||||
// Put assignment before the referencing statement
|
||||
insertBeforeStmt(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, nodep});
|
||||
|
|
@ -158,8 +159,9 @@ private:
|
|||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
VL_RESTORER(m_cfuncp);
|
||||
VL_RESTORER(m_tmpVarCnt);
|
||||
m_cfuncp = nodep;
|
||||
m_tempNames.reset();
|
||||
m_tmpVarCnt = 0;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +214,7 @@ private:
|
|||
}
|
||||
iterateAndNextNull(nodep->rhsp());
|
||||
{
|
||||
VL_RESTORER(m_assignLhs);
|
||||
// VL_RESTORER(m_assignLhs); // Not needed; part of RESTORER_START_STATEMENT()
|
||||
m_assignLhs = true;
|
||||
iterateAndNextNull(nodep->lhsp());
|
||||
}
|
||||
|
|
@ -368,7 +370,7 @@ private:
|
|||
UINFO(4, "Autoflush " << nodep << endl);
|
||||
nodep->addNextHere(
|
||||
new AstFFlush{nodep->fileline(),
|
||||
VN_AS(AstNode::cloneTreeNull(nodep->filep(), true), NodeExpr)});
|
||||
nodep->filep() ? nodep->filep()->cloneTree(true) : nullptr});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -391,10 +393,7 @@ private:
|
|||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit PremitVisitor(AstNetlist* nodep)
|
||||
: m_tempNames{"__Vtemp"} {
|
||||
iterate(nodep);
|
||||
}
|
||||
explicit PremitVisitor(AstNetlist* nodep) { iterate(nodep); }
|
||||
~PremitVisitor() override {
|
||||
V3Stats::addStat("Optimizations, Prelim extracted value to ConstPool",
|
||||
m_extractedToConstPool);
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ private:
|
|||
const VNUser2InUse m_inuser2;
|
||||
|
||||
// STATE
|
||||
VMemberMap memberMap; // Member names cached for fast lookup
|
||||
VMemberMap m_memberMap; // Member names cached for fast lookup
|
||||
AstNodeModule* m_modp = nullptr; // Current module
|
||||
const AstNodeFTask* m_ftaskp = nullptr; // Current function/task
|
||||
size_t m_enumValueTabCount = 0; // Number of tables with enum values created
|
||||
|
|
@ -215,7 +215,7 @@ private:
|
|||
}
|
||||
}
|
||||
void addPrePostCall(AstClass* classp, AstFunc* funcp, const string& name) {
|
||||
if (AstTask* userFuncp = VN_CAST(memberMap.findMember(classp, name), Task)) {
|
||||
if (AstTask* userFuncp = VN_CAST(m_memberMap.findMember(classp, name), Task)) {
|
||||
AstTaskRef* const callp
|
||||
= new AstTaskRef{userFuncp->fileline(), userFuncp->name(), nullptr};
|
||||
callp->taskp(userFuncp);
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ private:
|
|||
m_mgIndexHi = lindex;
|
||||
UINFO(9, "Start merge i=" << lindex << " o=" << m_mgOffset << nodep << endl);
|
||||
}
|
||||
void visit(AstExprStmt* nodep) override { iterateChildren(nodep); }
|
||||
//--------------------
|
||||
void visit(AstVar*) override {} // Accelerate
|
||||
void visit(AstNodeExpr*) override {} // Accelerate
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Simple and efficient Run-Time Type Information
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2023 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#ifndef VERILATOR_V3RTTI_H_
|
||||
#define VERILATOR_V3RTTI_H_
|
||||
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
// Holds list of types as template parameter pack.
|
||||
// Useful in compile-time code generation.
|
||||
template <typename... TN>
|
||||
struct VTypeList {
|
||||
template <typename... UN>
|
||||
constexpr VTypeList<TN..., UN...> operator+(VTypeList<UN...>) const {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// Holds one type.
|
||||
// Can be safely used as a return or argument type, and even instantiated, without triggering any
|
||||
// potential limitations or effects of the held type.
|
||||
template <typename T>
|
||||
struct VTypeWrapper {
|
||||
using type_t = T;
|
||||
};
|
||||
|
||||
// Implementation details of other constructs defined in this header.
|
||||
namespace V3RttiInternal {
|
||||
|
||||
// Helper function for extracting first type from VTypeList.
|
||||
template <typename T0, typename... TN>
|
||||
static inline constexpr VTypeWrapper<T0> vlTypeListFront(VTypeList<T0, TN...>) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Overload for empty type list. Returns false.
|
||||
inline static constexpr bool isClassIdOfOneOf(uintptr_t id, VTypeList<>) VL_PURE { return false; }
|
||||
|
||||
// Returns true iff `id` has the same value as `T::rttiClassId()`, where `T` is any type held by
|
||||
// `VTypeList` object passed as the second argument.
|
||||
template <typename Base0, typename... BaseN>
|
||||
inline static constexpr bool isClassIdOfOneOf(uintptr_t id, VTypeList<Base0, BaseN...>) VL_PURE {
|
||||
return id == Base0::rttiClassId() || isClassIdOfOneOf(id, VTypeList<BaseN...>{});
|
||||
}
|
||||
|
||||
} // namespace V3RttiInternal
|
||||
|
||||
// Alias for the first (frontmost) type held by type list `TL`.
|
||||
template <typename TL>
|
||||
using VTypeListFront = typename decltype(::V3RttiInternal::vlTypeListFront(TL{}))::type_t;
|
||||
|
||||
// `VTypeList` holding types from type lists `TL1` followed by types from type list `TL2`.
|
||||
template <typename TL1, typename TL2>
|
||||
using VJoinedTypeLists = decltype(TL1{} + TL2{});
|
||||
|
||||
// Common code used by VL_RTTI_COMMON_IMPL and VL_RTTI_COMMON_IMPL_BASE.
|
||||
#define V3RTTIINTERNAL_VL_RTTI_COMMON_IMPL(ThisClass) \
|
||||
private: \
|
||||
/* A type used only for implementation of the static_assert below. */ \
|
||||
struct RttiUniqueTypeForThisClass {}; \
|
||||
static_assert( \
|
||||
std::is_same<RttiUniqueTypeForThisClass, ThisClass::RttiUniqueTypeForThisClass>::value, \
|
||||
"'ThisClass' argument (" #ThisClass ") does not match the class name"); \
|
||||
\
|
||||
public: \
|
||||
/* Returns unique ID of the class. Useful with `isInstanceOfClassWithId()` method. */ \
|
||||
static uintptr_t rttiClassId() VL_PURE { \
|
||||
/* The only purpose of the following variable is to occupy an unique memory address. */ \
|
||||
/* This address is used as an unique class ID. */ \
|
||||
static char aStaticVariable; \
|
||||
return reinterpret_cast<uintptr_t>(&aStaticVariable); \
|
||||
}
|
||||
|
||||
// Call this macro at the beginning of class definition if the class derives from a
|
||||
// class with VL_RTTI_IMPL or VL_RTTI_IMPL_BASE calls.
|
||||
#define VL_RTTI_IMPL(ThisClass, DirectBaseClass) \
|
||||
V3RTTIINTERNAL_VL_RTTI_COMMON_IMPL(ThisClass) \
|
||||
static_assert( \
|
||||
std::is_same<DirectBaseClass, \
|
||||
VTypeListFront<DirectBaseClass::RttiThisAndBaseClassesList>>::value, \
|
||||
"Missing VL_RTTI_IMPL(...) in the direct base class (" #DirectBaseClass ")"); \
|
||||
\
|
||||
public: \
|
||||
/* Type list containing this class and all classes from the inheritance chain. */ \
|
||||
using RttiThisAndBaseClassesList \
|
||||
= VJoinedTypeLists<VTypeList<ThisClass>, \
|
||||
typename DirectBaseClass::RttiThisAndBaseClassesList>; \
|
||||
\
|
||||
protected: \
|
||||
/* Returns true iff `id` has the same value as `T::rttiClassId()`, where `T` is either this \
|
||||
* class or any class from this class' inheritance chain. */ \
|
||||
bool isInstanceOfClassWithId(uintptr_t id) const override VL_PURE { \
|
||||
return ::V3RttiInternal::isClassIdOfOneOf(id, RttiThisAndBaseClassesList{}); \
|
||||
} \
|
||||
\
|
||||
private: /* Revert to private visibility after this macro */
|
||||
|
||||
// Call this macro at the beginning of a base class to implement class type queries using
|
||||
// `p->isInstanceOfClassWithId(ClassName::rttiClassId())`.
|
||||
#define VL_RTTI_IMPL_BASE(ThisClass) \
|
||||
V3RTTIINTERNAL_VL_RTTI_COMMON_IMPL(ThisClass) \
|
||||
public: \
|
||||
/* Type list containing this class and all classes from the inheritance chain. */ \
|
||||
using RttiThisAndBaseClassesList = VTypeList<ThisClass>; \
|
||||
\
|
||||
protected: \
|
||||
/* Returns true iff `id` has the same value as value returned by this class' \
|
||||
`rttiClassId()` method. */ \
|
||||
virtual bool isInstanceOfClassWithId(uintptr_t id) const VL_PURE { \
|
||||
return id == rttiClassId(); \
|
||||
} \
|
||||
\
|
||||
private: /* Revert to private visibility after this macro */
|
||||
|
||||
#endif // Guard
|
||||
|
|
@ -119,29 +119,29 @@ void invertAndMergeSenTreeMap(
|
|||
//============================================================================
|
||||
// Split large function according to --output-split-cfuncs
|
||||
|
||||
std::map<AstCFunc*, int> s_funcNums; // What split number to attach to a function
|
||||
|
||||
AstCFunc* splitCheckCreateNewSubFunc(AstCFunc* ofuncp) {
|
||||
auto funcNumItMatch = s_funcNums.emplace(std::make_pair(ofuncp, 0));
|
||||
AstCFunc* const subFuncp = new AstCFunc{
|
||||
ofuncp->fileline(), ofuncp->name() + "__" + cvtToStr(funcNumItMatch.first->second++),
|
||||
ofuncp->scopep()};
|
||||
subFuncp->dontCombine(true);
|
||||
subFuncp->isStatic(false);
|
||||
subFuncp->isLoose(true);
|
||||
subFuncp->slow(ofuncp->slow());
|
||||
subFuncp->declPrivate(ofuncp->declPrivate());
|
||||
return subFuncp;
|
||||
};
|
||||
|
||||
void splitCheck(AstCFunc* ofuncp) {
|
||||
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
|
||||
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
|
||||
|
||||
int funcnum = 0;
|
||||
int func_stmts = 0;
|
||||
const bool is_ofuncp_coroutine = ofuncp->isCoroutine();
|
||||
AstCFunc* funcp = nullptr;
|
||||
|
||||
const auto createNewSubFuncp = [&]() {
|
||||
AstCFunc* const subFuncp = new AstCFunc{
|
||||
ofuncp->fileline(), ofuncp->name() + "__" + cvtToStr(funcnum++), ofuncp->scopep()};
|
||||
subFuncp->dontCombine(true);
|
||||
subFuncp->isStatic(false);
|
||||
subFuncp->isLoose(true);
|
||||
subFuncp->slow(ofuncp->slow());
|
||||
subFuncp->declPrivate(ofuncp->declPrivate());
|
||||
|
||||
func_stmts = 0;
|
||||
|
||||
return subFuncp;
|
||||
};
|
||||
|
||||
const auto finishSubFuncp = [&](AstCFunc* subFuncp) {
|
||||
ofuncp->scopep()->addBlocksp(subFuncp);
|
||||
AstCCall* const callp = new AstCCall{subFuncp->fileline(), subFuncp};
|
||||
|
|
@ -160,7 +160,8 @@ void splitCheck(AstCFunc* ofuncp) {
|
|||
}
|
||||
};
|
||||
|
||||
funcp = createNewSubFuncp();
|
||||
funcp = splitCheckCreateNewSubFunc(ofuncp);
|
||||
func_stmts = 0;
|
||||
|
||||
// Unlink all statements, then add item by item to new sub-functions
|
||||
AstBegin* const tempp = new AstBegin{ofuncp->fileline(), "[EditWrapper]",
|
||||
|
|
@ -173,7 +174,8 @@ void splitCheck(AstCFunc* ofuncp) {
|
|||
|
||||
if ((func_stmts + stmts) > v3Global.opt.outputSplitCFuncs()) {
|
||||
finishSubFuncp(funcp);
|
||||
funcp = createNewSubFuncp();
|
||||
funcp = splitCheckCreateNewSubFunc(ofuncp);
|
||||
func_stmts = 0;
|
||||
}
|
||||
|
||||
funcp->addStmtsp(itemp);
|
||||
|
|
@ -391,7 +393,6 @@ AstSenTree* createTriggerSenTree(AstNetlist* netlistp, AstVarScope* const vscp,
|
|||
AstCMethodHard* const callp
|
||||
= new AstCMethodHard{flp, vrefp, "word", new AstConst{flp, wordIndex}};
|
||||
callp->dtypeSetUInt64();
|
||||
callp->pure(true);
|
||||
AstNodeExpr* const termp
|
||||
= new AstAnd{flp, new AstConst{flp, AstConst::Unsized64{}, 1ULL << bitIndex}, callp};
|
||||
AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, termp};
|
||||
|
|
@ -477,7 +478,6 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|||
AstCMethodHard* const callp
|
||||
= new AstCMethodHard{flp, vrefp, "word", new AstConst{flp, wordIndex}};
|
||||
callp->dtypeSetUInt64();
|
||||
callp->pure(true);
|
||||
AstNodeExpr* const termp
|
||||
= new AstAnd{flp, new AstConst{flp, AstConst::Unsized64{}, 1ULL << bitIndex}, callp};
|
||||
return termp;
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ void transformForks(AstNetlist* const netlistp);
|
|||
void schedule(AstNetlist*);
|
||||
|
||||
// Sub-steps
|
||||
LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic);
|
||||
LogicByScope breakCycles(AstNetlist* netlistp, const LogicByScope& combinationalLogic);
|
||||
LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
|
||||
LogicByScope& hybridLogic);
|
||||
LogicReplicas replicateLogic(LogicRegions&);
|
||||
|
|
|
|||
|
|
@ -59,17 +59,18 @@ namespace {
|
|||
// ##############################################################################
|
||||
// Data structures (graph types)
|
||||
|
||||
class LogicVertex final : public V3GraphVertex {
|
||||
class SchedAcyclicLogicVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(SchedAcyclicLogicVertex, V3GraphVertex)
|
||||
AstNode* const m_logicp; // The logic node this vertex represents
|
||||
AstScope* const m_scopep; // The enclosing AstScope of the logic node
|
||||
|
||||
public:
|
||||
LogicVertex(V3Graph* graphp, AstNode* logicp, AstScope* scopep)
|
||||
SchedAcyclicLogicVertex(V3Graph* graphp, AstNode* logicp, AstScope* scopep)
|
||||
: V3GraphVertex{graphp}
|
||||
, m_logicp{logicp}
|
||||
, m_scopep{scopep} {}
|
||||
V3GraphVertex* clone(V3Graph* graphp) const override {
|
||||
return new LogicVertex{graphp, logicp(), scopep()};
|
||||
return new SchedAcyclicLogicVertex{graphp, logicp(), scopep()};
|
||||
}
|
||||
|
||||
AstNode* logicp() const { return m_logicp; }
|
||||
|
|
@ -81,16 +82,19 @@ public:
|
|||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
class VarVertex final : public V3GraphVertex {
|
||||
class SchedAcyclicVarVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(SchedAcyclicVarVertex, V3GraphVertex)
|
||||
AstVarScope* const m_vscp; // The AstVarScope this vertex represents
|
||||
|
||||
public:
|
||||
VarVertex(V3Graph* graphp, AstVarScope* vscp)
|
||||
SchedAcyclicVarVertex(V3Graph* graphp, AstVarScope* vscp)
|
||||
: V3GraphVertex{graphp}
|
||||
, m_vscp{vscp} {}
|
||||
AstVarScope* vscp() const { return m_vscp; }
|
||||
AstVar* varp() const { return m_vscp->varp(); }
|
||||
V3GraphVertex* clone(V3Graph* graphp) const override { return new VarVertex{graphp, vscp()}; }
|
||||
V3GraphVertex* clone(V3Graph* graphp) const override {
|
||||
return new SchedAcyclicVarVertex{graphp, vscp()};
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START // Debug code
|
||||
string name() const override VL_MT_STABLE { return m_vscp->name(); }
|
||||
|
|
@ -102,13 +106,12 @@ public:
|
|||
class Graph final : public V3Graph {
|
||||
void loopsVertexCb(V3GraphVertex* vtxp) override {
|
||||
// TODO: 'typeName' is an internal thing. This should be more human readable.
|
||||
if (LogicVertex* const lvtxp = dynamic_cast<LogicVertex*>(vtxp)) {
|
||||
if (SchedAcyclicLogicVertex* const lvtxp = vtxp->cast<SchedAcyclicLogicVertex>()) {
|
||||
AstNode* const logicp = lvtxp->logicp();
|
||||
std::cerr << logicp->fileline()->warnOtherStandalone()
|
||||
<< " Example path: " << logicp->typeName() << endl;
|
||||
} else {
|
||||
VarVertex* const vvtxp = dynamic_cast<VarVertex*>(vtxp);
|
||||
UASSERT(vvtxp, "Cannot be anything else");
|
||||
SchedAcyclicVarVertex* const vvtxp = vtxp->as<SchedAcyclicVarVertex>();
|
||||
AstVarScope* const vscp = vvtxp->vscp();
|
||||
std::cerr << vscp->fileline()->warnOtherStandalone()
|
||||
<< " Example path: " << vscp->prettyName() << endl;
|
||||
|
|
@ -125,8 +128,8 @@ std::unique_ptr<Graph> buildGraph(const LogicByScope& lbs) {
|
|||
// AstVarScope::user1() -> VarVertx
|
||||
const VNUser1InUse user1InUse;
|
||||
const auto getVarVertex = [&](AstVarScope* vscp) {
|
||||
if (!vscp->user1p()) vscp->user1p(new VarVertex{graphp.get(), vscp});
|
||||
return vscp->user1u().to<VarVertex*>();
|
||||
if (!vscp->user1p()) vscp->user1p(new SchedAcyclicVarVertex{graphp.get(), vscp});
|
||||
return vscp->user1u().to<SchedAcyclicVarVertex*>();
|
||||
};
|
||||
|
||||
const auto addEdge = [&](V3GraphVertex* fromp, V3GraphVertex* top, int weight, bool cuttable) {
|
||||
|
|
@ -141,13 +144,14 @@ std::unique_ptr<Graph> buildGraph(const LogicByScope& lbs) {
|
|||
// Can safely ignore Postponed as we generate them all
|
||||
if (VN_IS(nodep, AlwaysPostponed)) continue;
|
||||
|
||||
LogicVertex* const lvtxp = new LogicVertex{graphp.get(), nodep, scopep};
|
||||
SchedAcyclicLogicVertex* const lvtxp
|
||||
= new SchedAcyclicLogicVertex{graphp.get(), nodep, scopep};
|
||||
const VNUser2InUse user2InUse;
|
||||
const VNUser3InUse user3InUse;
|
||||
|
||||
nodep->foreach([&](AstVarRef* refp) {
|
||||
AstVarScope* const vscp = refp->varScopep();
|
||||
VarVertex* const vvtxp = getVarVertex(vscp);
|
||||
SchedAcyclicVarVertex* const vvtxp = getVarVertex(vscp);
|
||||
// We want to cut the narrowest signals
|
||||
const int weight = vscp->width() / 8 + 1;
|
||||
// If written, add logic -> var edge
|
||||
|
|
@ -208,7 +212,7 @@ void removeNonCyclic(Graph* graphp) {
|
|||
}
|
||||
|
||||
// Has this VarVertex been cut? (any edges in or out has been cut)
|
||||
bool isCut(const VarVertex* vtxp) {
|
||||
bool isCut(const SchedAcyclicVarVertex* vtxp) {
|
||||
for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
if (edgep->weight() == 0) return true;
|
||||
}
|
||||
|
|
@ -218,33 +222,33 @@ bool isCut(const VarVertex* vtxp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::vector<VarVertex*> findCutVertices(Graph* graphp) {
|
||||
std::vector<VarVertex*> result;
|
||||
std::vector<SchedAcyclicVarVertex*> findCutVertices(Graph* graphp) {
|
||||
std::vector<SchedAcyclicVarVertex*> result;
|
||||
const VNUser1InUse user1InUse; // bool: already added to result
|
||||
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
|
||||
if (VarVertex* const vvtxp = dynamic_cast<VarVertex*>(vtxp)) {
|
||||
if (SchedAcyclicVarVertex* const vvtxp = vtxp->cast<SchedAcyclicVarVertex>()) {
|
||||
if (!vvtxp->vscp()->user1SetOnce() && isCut(vvtxp)) result.push_back(vvtxp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void resetEdgeWeights(const std::vector<VarVertex*>& cutVertices) {
|
||||
for (VarVertex* const vvtxp : cutVertices) {
|
||||
void resetEdgeWeights(const std::vector<SchedAcyclicVarVertex*>& cutVertices) {
|
||||
for (SchedAcyclicVarVertex* const vvtxp : cutVertices) {
|
||||
for (V3GraphEdge* ep = vvtxp->inBeginp(); ep; ep = ep->inNextp()) ep->weight(1);
|
||||
for (V3GraphEdge* ep = vvtxp->outBeginp(); ep; ep = ep->outNextp()) ep->weight(1);
|
||||
}
|
||||
}
|
||||
|
||||
// A VarVertex together with its fanout
|
||||
using Candidate = std::pair<VarVertex*, unsigned>;
|
||||
using Candidate = std::pair<SchedAcyclicVarVertex*, unsigned>;
|
||||
|
||||
// Gather all splitting candidates that are in the same SCC as the given vertex
|
||||
void gatherSCCCandidates(V3GraphVertex* vtxp, std::vector<Candidate>& candidates) {
|
||||
if (vtxp->user()) return; // Already done
|
||||
vtxp->user(true);
|
||||
|
||||
if (VarVertex* const vvtxp = dynamic_cast<VarVertex*>(vtxp)) {
|
||||
if (SchedAcyclicVarVertex* const vvtxp = vtxp->cast<SchedAcyclicVarVertex>()) {
|
||||
AstVar* const varp = vvtxp->varp();
|
||||
const string name = varp->prettyName();
|
||||
if (!varp->user3SetOnce() // Only consider each AstVar once
|
||||
|
|
@ -270,7 +274,7 @@ void gatherSCCCandidates(V3GraphVertex* vtxp, std::vector<Candidate>& candidates
|
|||
}
|
||||
|
||||
// Find all variables in a loop (SCC) that are candidates for splitting to break loops.
|
||||
void reportLoopVars(Graph* graphp, VarVertex* vvtxp) {
|
||||
void reportLoopVars(Graph* graphp, SchedAcyclicVarVertex* vvtxp) {
|
||||
// Vector of variables in UNOPTFLAT loop that are candidates for splitting.
|
||||
std::vector<Candidate> candidates;
|
||||
{
|
||||
|
|
@ -325,8 +329,8 @@ void reportLoopVars(Graph* graphp, VarVertex* vvtxp) {
|
|||
V3Stats::addStat("Scheduling, split_var, candidates", splittable);
|
||||
}
|
||||
|
||||
void reportCycles(Graph* graphp, const std::vector<VarVertex*>& cutVertices) {
|
||||
for (VarVertex* vvtxp : cutVertices) {
|
||||
void reportCycles(Graph* graphp, const std::vector<SchedAcyclicVarVertex*>& cutVertices) {
|
||||
for (SchedAcyclicVarVertex* vvtxp : cutVertices) {
|
||||
AstVarScope* const vscp = vvtxp->vscp();
|
||||
FileLine* const flp = vscp->fileline();
|
||||
|
||||
|
|
@ -350,16 +354,18 @@ void reportCycles(Graph* graphp, const std::vector<VarVertex*>& cutVertices) {
|
|||
}
|
||||
}
|
||||
|
||||
LogicByScope fixCuts(AstNetlist* netlistp, const std::vector<VarVertex*>& cutVertices) {
|
||||
LogicByScope fixCuts(AstNetlist* netlistp,
|
||||
const std::vector<SchedAcyclicVarVertex*>& cutVertices) {
|
||||
// For all logic that reads a cut vertex, build a map from logic -> list of cut AstVarScope
|
||||
// they read. Also build a vector of the involved logic for deterministic results.
|
||||
std::unordered_map<LogicVertex*, std::vector<AstVarScope*>> lvtx2Cuts;
|
||||
std::vector<LogicVertex*> lvtxps;
|
||||
std::unordered_map<SchedAcyclicLogicVertex*, std::vector<AstVarScope*>> lvtx2Cuts;
|
||||
std::vector<SchedAcyclicLogicVertex*> lvtxps;
|
||||
{
|
||||
const VNUser1InUse user1InUse; // bool: already added to 'lvtxps'
|
||||
for (VarVertex* const vvtxp : cutVertices) {
|
||||
for (SchedAcyclicVarVertex* const vvtxp : cutVertices) {
|
||||
for (V3GraphEdge* edgep = vvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
LogicVertex* const lvtxp = static_cast<LogicVertex*>(edgep->top());
|
||||
SchedAcyclicLogicVertex* const lvtxp
|
||||
= static_cast<SchedAcyclicLogicVertex*>(edgep->top());
|
||||
if (!lvtxp->logicp()->user1SetOnce()) lvtxps.push_back(lvtxp);
|
||||
lvtx2Cuts[lvtxp].push_back(vvtxp->vscp());
|
||||
}
|
||||
|
|
@ -370,7 +376,7 @@ LogicByScope fixCuts(AstNetlist* netlistp, const std::vector<VarVertex*>& cutVer
|
|||
// explicit additional triggers on the cut variables)
|
||||
LogicByScope result;
|
||||
SenTreeFinder finder{netlistp};
|
||||
for (LogicVertex* const lvtxp : lvtxps) {
|
||||
for (SchedAcyclicLogicVertex* const lvtxp : lvtxps) {
|
||||
AstNode* const logicp = lvtxp->logicp();
|
||||
logicp->unlinkFrBack();
|
||||
FileLine* const flp = logicp->fileline();
|
||||
|
|
@ -392,7 +398,7 @@ LogicByScope fixCuts(AstNetlist* netlistp, const std::vector<VarVertex*>& cutVer
|
|||
|
||||
} // namespace
|
||||
|
||||
LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic) {
|
||||
LogicByScope breakCycles(AstNetlist* netlistp, const LogicByScope& combinationalLogic) {
|
||||
// Build the dataflow (dependency) graph
|
||||
const std::unique_ptr<Graph> graphp = buildGraph(combinationalLogic);
|
||||
|
||||
|
|
@ -412,7 +418,7 @@ LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic)
|
|||
graphp->acyclic(&V3GraphEdge::followAlwaysTrue);
|
||||
|
||||
// Find all cut vertices
|
||||
const std::vector<VarVertex*> cutVertices = findCutVertices(graphp.get());
|
||||
const std::vector<SchedAcyclicVarVertex*> cutVertices = findCutVertices(graphp.get());
|
||||
|
||||
// Reset edge weights for reporting
|
||||
resetEdgeWeights(cutVertices);
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ namespace V3Sched {
|
|||
namespace {
|
||||
|
||||
class SchedSenVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(SchedSenVertex, V3GraphVertex)
|
||||
const AstSenItem* const m_senItemp;
|
||||
|
||||
public:
|
||||
|
|
@ -74,6 +75,7 @@ public:
|
|||
};
|
||||
|
||||
class SchedLogicVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(SchedLogicVertex, V3GraphVertex)
|
||||
AstScope* const m_scopep;
|
||||
AstSenTree* const m_senTreep;
|
||||
AstNode* const m_logicp;
|
||||
|
|
@ -97,6 +99,7 @@ public:
|
|||
};
|
||||
|
||||
class SchedVarVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(SchedVarVertex, V3GraphVertex)
|
||||
const AstVarScope* const m_vscp;
|
||||
|
||||
public:
|
||||
|
|
@ -299,7 +302,7 @@ void colorActiveRegion(const V3Graph& graph) {
|
|||
|
||||
// Trace from all SchedSenVertex
|
||||
for (V3GraphVertex* vtxp = graph.verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
|
||||
if (const auto activeEventVtxp = dynamic_cast<SchedSenVertex*>(vtxp)) {
|
||||
if (const auto activeEventVtxp = vtxp->cast<SchedSenVertex>()) {
|
||||
queue.push_back(activeEventVtxp);
|
||||
}
|
||||
}
|
||||
|
|
@ -323,9 +326,9 @@ void colorActiveRegion(const V3Graph& graph) {
|
|||
// If this is a logic vertex, also enqueue all variable vertices that are driven from this
|
||||
// logic. This will ensure that if a variable is set in the active region, then all
|
||||
// settings of that variable will be in the active region.
|
||||
if (dynamic_cast<SchedLogicVertex*>(&vtx)) {
|
||||
if (vtx.is<SchedLogicVertex>()) {
|
||||
for (V3GraphEdge* edgep = vtx.outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
UASSERT(dynamic_cast<SchedVarVertex*>(edgep->top()), "Should be var vertex");
|
||||
UASSERT(edgep->top()->is<SchedVarVertex>(), "Should be var vertex");
|
||||
queue.push_back(edgep->top());
|
||||
}
|
||||
}
|
||||
|
|
@ -350,7 +353,7 @@ LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLo
|
|||
LogicRegions result;
|
||||
|
||||
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
|
||||
if (const auto lvtxp = dynamic_cast<SchedLogicVertex*>(vtxp)) {
|
||||
if (const auto lvtxp = vtxp->cast<SchedLogicVertex>()) {
|
||||
LogicByScope& lbs = lvtxp->color() ? result.m_act : result.m_nba;
|
||||
AstNode* const logicp = lvtxp->logicp();
|
||||
logicp->unlinkFrBack();
|
||||
|
|
|
|||
|
|
@ -59,11 +59,12 @@ enum RegionFlags : uint8_t {
|
|||
//##############################################################################
|
||||
// Data structures (graph types)
|
||||
|
||||
class Vertex VL_NOT_FINAL : public V3GraphVertex {
|
||||
RegionFlags m_drivingRegions{NONE}; // The regions driving this vertex
|
||||
class SchedReplicateVertex VL_NOT_FINAL : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(SchedReplicateVertex, V3GraphVertex)
|
||||
RegionFlags m_drivingRegions{RegionFlags::NONE}; // The regions driving this vertex
|
||||
|
||||
public:
|
||||
explicit Vertex(V3Graph* graphp)
|
||||
explicit SchedReplicateVertex(V3Graph* graphp)
|
||||
: V3GraphVertex{graphp} {}
|
||||
uint8_t drivingRegions() const { return m_drivingRegions; }
|
||||
void addDrivingRegions(uint8_t regions) {
|
||||
|
|
@ -87,16 +88,17 @@ public:
|
|||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
class LogicVertex final : public Vertex {
|
||||
class SchedReplicateLogicVertex final : public SchedReplicateVertex {
|
||||
VL_RTTI_IMPL(SchedReplicateLogicVertex, SchedReplicateVertex)
|
||||
AstScope* const m_scopep; // The enclosing AstScope of the logic node
|
||||
AstSenTree* const m_senTreep; // The sensitivity of the logic node
|
||||
AstNode* const m_logicp; // The logic node this vertex represents
|
||||
RegionFlags const m_assignedRegion; // The region this logic is originally assigned to
|
||||
|
||||
public:
|
||||
LogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep, AstNode* logicp,
|
||||
RegionFlags assignedRegion)
|
||||
: Vertex{graphp}
|
||||
SchedReplicateLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep,
|
||||
AstNode* logicp, RegionFlags assignedRegion)
|
||||
: SchedReplicateVertex{graphp}
|
||||
, m_scopep{scopep}
|
||||
, m_senTreep{senTreep}
|
||||
, m_logicp{logicp}
|
||||
|
|
@ -113,12 +115,13 @@ public:
|
|||
string dotShape() const override { return "rectangle"; }
|
||||
};
|
||||
|
||||
class VarVertex final : public Vertex {
|
||||
class SchedReplicateVarVertex final : public SchedReplicateVertex {
|
||||
VL_RTTI_IMPL(SchedReplicateVarVertex, SchedReplicateVertex)
|
||||
AstVarScope* const m_vscp; // The AstVarScope this vertex represents
|
||||
|
||||
public:
|
||||
VarVertex(V3Graph* graphp, AstVarScope* vscp)
|
||||
: Vertex{graphp}
|
||||
SchedReplicateVarVertex(V3Graph* graphp, AstVarScope* vscp)
|
||||
: SchedReplicateVertex{graphp}
|
||||
, m_vscp{vscp} {
|
||||
// Top level inputs are
|
||||
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()) {
|
||||
|
|
@ -149,11 +152,11 @@ std::unique_ptr<Graph> buildGraph(const LogicRegions& logicRegions) {
|
|||
// AstVarScope::user1() -> VarVertx
|
||||
const VNUser1InUse user1InUse;
|
||||
const auto getVarVertex = [&](AstVarScope* vscp) {
|
||||
if (!vscp->user1p()) vscp->user1p(new VarVertex{graphp.get(), vscp});
|
||||
return vscp->user1u().to<VarVertex*>();
|
||||
if (!vscp->user1p()) vscp->user1p(new SchedReplicateVarVertex{graphp.get(), vscp});
|
||||
return vscp->user1u().to<SchedReplicateVarVertex*>();
|
||||
};
|
||||
|
||||
const auto addEdge = [&](Vertex* fromp, Vertex* top) {
|
||||
const auto addEdge = [&](SchedReplicateVertex* fromp, SchedReplicateVertex* top) {
|
||||
new V3GraphEdge{graphp.get(), fromp, top, 1};
|
||||
};
|
||||
|
||||
|
|
@ -182,14 +185,14 @@ std::unique_ptr<Graph> buildGraph(const LogicRegions& logicRegions) {
|
|||
}
|
||||
|
||||
for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
LogicVertex* const lvtxp
|
||||
= new LogicVertex{graphp.get(), scopep, senTreep, nodep, region};
|
||||
SchedReplicateLogicVertex* const lvtxp
|
||||
= new SchedReplicateLogicVertex{graphp.get(), scopep, senTreep, nodep, region};
|
||||
const VNUser2InUse user2InUse;
|
||||
const VNUser3InUse user3InUse;
|
||||
|
||||
nodep->foreach([&](AstVarRef* refp) {
|
||||
AstVarScope* const vscp = refp->varScopep();
|
||||
VarVertex* const vvtxp = getVarVertex(vscp);
|
||||
SchedReplicateVarVertex* const vvtxp = getVarVertex(vscp);
|
||||
|
||||
// If read, add var -> logic edge
|
||||
// Note: Use same heuristic as ordering does to ignore written variables
|
||||
|
|
@ -216,7 +219,7 @@ std::unique_ptr<Graph> buildGraph(const LogicRegions& logicRegions) {
|
|||
return graphp;
|
||||
}
|
||||
|
||||
void propagateDrivingRegions(Vertex* vtxp) {
|
||||
void propagateDrivingRegions(SchedReplicateVertex* vtxp) {
|
||||
// Note: The graph is always acyclic, so the recursion will terminate
|
||||
|
||||
// Nothing to do if already visited
|
||||
|
|
@ -225,7 +228,7 @@ void propagateDrivingRegions(Vertex* vtxp) {
|
|||
// Compute union of driving regions of all inputs
|
||||
uint8_t drivingRegions = 0;
|
||||
for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
Vertex* const srcp = static_cast<Vertex*>(edgep->fromp());
|
||||
SchedReplicateVertex* const srcp = edgep->fromp()->as<SchedReplicateVertex>();
|
||||
propagateDrivingRegions(srcp);
|
||||
drivingRegions |= srcp->drivingRegions();
|
||||
}
|
||||
|
|
@ -240,7 +243,7 @@ void propagateDrivingRegions(Vertex* vtxp) {
|
|||
LogicReplicas replicate(Graph* graphp) {
|
||||
LogicReplicas result;
|
||||
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
|
||||
if (LogicVertex* const lvtxp = dynamic_cast<LogicVertex*>(vtxp)) {
|
||||
if (SchedReplicateLogicVertex* const lvtxp = vtxp->cast<SchedReplicateLogicVertex>()) {
|
||||
const auto replicateTo = [&](LogicByScope& lbs) {
|
||||
lbs.add(lvtxp->scopep(), lvtxp->senTreep(), lvtxp->logicp()->cloneTree(false));
|
||||
};
|
||||
|
|
@ -264,7 +267,7 @@ LogicReplicas replicateLogic(LogicRegions& logicRegionsRegions) {
|
|||
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("sched-replicate");
|
||||
// Propagate driving region flags
|
||||
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
|
||||
propagateDrivingRegions(static_cast<Vertex*>(vtxp));
|
||||
propagateDrivingRegions(vtxp->as<SchedReplicateVertex>());
|
||||
}
|
||||
// Dump for debug
|
||||
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("sched-replicate-propagated");
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
|
|||
m_writtenBySuspendable.push_back(nodep->varScopep());
|
||||
}
|
||||
}
|
||||
void visit(AstExprStmt* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
//--------------------
|
||||
void visit(AstNodeExpr*) override {} // Accelerate
|
||||
|
|
@ -361,8 +362,9 @@ void transformForks(AstNetlist* const netlistp) {
|
|||
UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name");
|
||||
// Create a function to put this begin's statements in
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstCFunc* const newfuncp
|
||||
= new AstCFunc{flp, nodep->name(), m_funcp->scopep(), "VlCoroutine"};
|
||||
AstCFunc* const newfuncp = new AstCFunc{
|
||||
flp, m_funcp->name() + "__" + nodep->name(), m_funcp->scopep(), "VlCoroutine"};
|
||||
|
||||
m_funcp->addNextHere(newfuncp);
|
||||
newfuncp->isLoose(m_funcp->isLoose());
|
||||
newfuncp->slow(m_funcp->slow());
|
||||
|
|
@ -405,6 +407,7 @@ void transformForks(AstNetlist* const netlistp) {
|
|||
if (nodep->funcp()->needProcess()) m_beginNeedProcess = true;
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
void visit(AstExprStmt* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
//--------------------
|
||||
void visit(AstNodeExpr*) override {} // Accelerate
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue