Merge from master for release.

This commit is contained in:
Wilson Snyder 2023-09-16 20:43:53 -04:00
commit 82e1dbe162
332 changed files with 81389 additions and 3213 deletions

View File

@ -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 != '' }}

2
.gitignore vendored
View File

@ -41,3 +41,5 @@ verilator-config-version.cmake
**/_build/*
**/obj_dir/*
/.vscode/
/.idea/
/cmake-build-*/

View File

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

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

View File

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

View File

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

View File

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

View File

@ -40,6 +40,7 @@ Fan Shupei
february cozzocrea
Felix Neumärker
Felix Yan
Frans Skarman
G-A. Kamendje
Garrett Smith
Geza Lore

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -568,6 +568,8 @@ public:
void addModel(VerilatedModel*);
VerilatedVirtualBase* threadPoolp();
void prepareClone();
VerilatedVirtualBase* threadPoolpOnClone();
VerilatedVirtualBase*
enableExecutionProfiler(VerilatedVirtualBase* (*construct)(VerilatedContext&));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -289,6 +289,7 @@ NON_STANDALONE_HEADERS = \
V3AstNodeExpr.h \
V3AstNodeOther.h \
V3DfgVertices.h \
V3ThreadPool.h \
V3WidthCommit.h \
AST_DEFS := \

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,9 +28,6 @@
#include <list>
#include <map>
// Compatibility with Verilog-Perl's preprocessor
#define fatalSrc(msg) v3fatalSrc(msg)
class VInFilter;
class VSpellCheck;

View File

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

View File

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

View File

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

131
src/V3Rtti.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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