Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays, event controls within processes (not just at the start), wait statements, and forks. Building a design with those constructs requires a compiler that supports C++20 coroutines (GCC 10, Clang 5). The basic idea is to have processes and tasks with delays/event controls implemented as C++20 coroutines. This allows us to suspend and resume them at any time. There are five main runtime classes responsible for managing suspended coroutines: * `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle` with move semantics and automatic cleanup. * `VlDelayScheduler`, for coroutines suspended by delays. It resumes them at a proper simulation time. * `VlTriggerScheduler`, for coroutines suspended by event controls. It resumes them if its corresponding trigger was set. * `VlForkSync`, used for syncing `fork..join` and `fork..join_any` blocks. * `VlCoroutine`, the return type of all verilated coroutines. It allows for suspending a stack of coroutines (normally, C++ coroutines are stackless). There is a new visitor in `V3Timing.cpp` which: * scales delays according to the timescale, * simplifies intra-assignment timing controls and net delays into regular timing controls and assignments, * simplifies wait statements into loops with event controls, * marks processes and tasks with timing controls in them as suspendable, * creates delay, trigger scheduler, and fork sync variables, * transforms timing controls and fork joins into C++ awaits There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`) that integrate static scheduling with timing. This involves providing external domains for variables, so that the necessary combinational logic gets triggered after coroutine resumption, as well as statements that need to be injected into the design eval function to perform this resumption at the correct time. There is also a function that transforms forked processes into separate functions. See the comments in `verilated_timing.h`, `verilated_timing.cpp`, `V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals documentation for more details. Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
This commit is contained in:
parent
7cc89b8b42
commit
39af5d020e
2
Changes
2
Changes
|
@ -17,6 +17,8 @@ Verilator 5.001 devel
|
|||
* Fully support the Active and NBA scheduling regions as defined by the
|
||||
SystemVerilog standard (IEEE 1800-2017 chapter 4). This means all generated
|
||||
clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD]
|
||||
* Support timing controls (delays, event controls in any location, wait
|
||||
statements) and forks. See docs for details. [Krzysztof Bieganski, Antmicro Ltd]
|
||||
|
||||
|
||||
Verilator 4.225 devel
|
||||
|
|
|
@ -341,6 +341,7 @@ detailed descriptions of these arguments.
|
|||
--lib-create <name> Create a DPI library
|
||||
+libext+<ext>+[ext]... Extensions for finding modules
|
||||
--lint-only Lint, but do not make output
|
||||
--main Generate a main C++ file
|
||||
--make <build-tool> Generate scripts for specified build tool
|
||||
-MAKEFLAGS <flags> Arguments to pass to make during --build
|
||||
--max-num-width <value> Maximum number width (default: 64K)
|
||||
|
@ -393,6 +394,8 @@ detailed descriptions of these arguments.
|
|||
--threads <threads> Enable multithreading
|
||||
--threads-dpi <mode> Enable multithreaded DPI
|
||||
--threads-max-mtasks <mtasks> Tune maximum mtask partitioning
|
||||
--timing Enable timing support
|
||||
--no-timing Disable timing support
|
||||
--timescale <timescale> Sets default timescale
|
||||
--timescale-override <timescale> Overrides all timescales
|
||||
--top <topname> Alias of --top-module
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive \
|
||||
|
|
|
@ -8,7 +8,7 @@ Verilator build. It uses the following parameters:
|
|||
|
||||
- Source revision (default: master)
|
||||
|
||||
- Compiler (GCC 9.3.0, clang 10.0.0, default: 9.3.0)
|
||||
- Compiler (GCC 10.3.0, clang 10.0.0, default: 10.3.0)
|
||||
|
||||
The container is published as ``verilator/verilator-buildenv`` on `docker
|
||||
hub
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive \
|
||||
|
|
30
configure.ac
30
configure.ac
|
@ -374,6 +374,36 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wlogical-op)
|
|||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wthread-safety)
|
||||
AC_SUBST(CFG_CXXFLAGS_WEXTRA)
|
||||
|
||||
# Flags for coroutine support for dynamic scheduling
|
||||
_MY_CXX_CHECK_IFELSE(
|
||||
-fcoroutines-ts,
|
||||
[CFG_CXXFLAGS_COROUTINES="-fcoroutines-ts"],
|
||||
[CFG_CXXFLAGS_COROUTINES="-fcoroutines"])
|
||||
AC_SUBST(CFG_CXXFLAGS_COROUTINES)
|
||||
|
||||
# HAVE_COROUTINES
|
||||
# Check if coroutines are supported at all
|
||||
AC_MSG_CHECKING([whether coroutines are supported by $CXX])
|
||||
ACO_SAVE_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $CFG_CXXFLAGS_COROUTINES"
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM([
|
||||
#ifdef __clang__
|
||||
#define __cpp_impl_coroutine 1
|
||||
#endif
|
||||
#include <coroutine>
|
||||
],[[]])],
|
||||
[_my_result=yes
|
||||
AC_DEFINE([HAVE_COROUTINES],[1],[Defined if coroutines are supported by $CXX])],
|
||||
[AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM([#include <experimental/coroutine>],[[]])],
|
||||
[_my_result=yes
|
||||
AC_DEFINE([HAVE_COROUTINES],[1],[Defined if coroutines are supported by $CXX])],
|
||||
[_my_result=no])])
|
||||
AC_MSG_RESULT($_my_result)
|
||||
CXXFLAGS="$ACO_SAVE_CXXFLAGS"
|
||||
AC_SUBST(HAVE_COROUTINES)
|
||||
|
||||
# Flags for compiling Verilator internals including parser always
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-Qunused-arguments)
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-faligned-new)
|
||||
|
|
|
@ -88,6 +88,7 @@ Nathan Kohagen
|
|||
Nathan Myers
|
||||
Patrick Stewart
|
||||
Paul Wright
|
||||
Pawel Sagan
|
||||
Peter Horvath
|
||||
Peter Monsson
|
||||
Philipp Wagner
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.. comment: generated by t_lint_stmtdly_bad
|
||||
.. code-block::
|
||||
|
||||
%Warning-STMTDLY: example.v:1:8 Unsupported: Ignoring delay on this delayed statement.
|
||||
%Warning-STMTDLY: example.v:1:8 Ignoring delay on this statement due to --no-timing
|
||||
|
|
|
@ -94,7 +94,10 @@ commented example.
|
|||
Top level IO signals are read and written as members of the model. You
|
||||
call the model's :code:`eval()` method to evaluate the model. When the
|
||||
simulation is complete call the model's :code:`final()` method to execute
|
||||
any SystemVerilog final blocks, and complete any assertions. See
|
||||
any SystemVerilog final blocks, and complete any assertions. If using
|
||||
:vlopt:`--timing`, there are two additional functions for checking if
|
||||
there are any events pending in the simulation due to delays, and for
|
||||
retrieving the simulation time of the next delayed event. See
|
||||
:ref:`Evaluation Loop`.
|
||||
|
||||
|
||||
|
@ -440,10 +443,24 @@ there is only a single design, you would call :code:`eval_step()` then
|
|||
:code:`eval_end_step()`; in fact :code:`eval()` described above is just a
|
||||
wrapper which calls these two functions.
|
||||
|
||||
3. If using delays and :vlopt:`--timing`, there are two additional methods
|
||||
the user should call:
|
||||
|
||||
* :code:`designp->eventsPending()`, which returns :code:`true` if there are
|
||||
any delayed events penging,
|
||||
* :code:`designp->nextTimeSlot()`, which returns the simulation time of the
|
||||
next delayed event. This method can only be called if
|
||||
:code:`designp->nextTimeSlot()` returned :code:`true`.
|
||||
Call :code:`eventsPending()` to check if you should continue with the
|
||||
simulation, and then :code:`nextTimeSlot()` to move simulation time forward.
|
||||
:vlopt:`--main` can be used with :vlopt:`--timing` to generate a basic example
|
||||
of a timing-enabled eval loop.
|
||||
|
||||
When :code:`eval()` (or :code:`eval_step()`) is called Verilator looks for
|
||||
changes in clock signals and evaluates related sequential always blocks,
|
||||
such as computing always_ff @ (posedge...) outputs. Then Verilator
|
||||
evaluates combinatorial logic.
|
||||
such as computing always_ff @ (posedge...) outputs. With :vlopt:`--timing`, it
|
||||
resumes any delayed processes awaiting the current simulation time. Then
|
||||
Verilator evaluates combinational logic.
|
||||
|
||||
Note combinatorial logic is not computed before sequential always blocks
|
||||
are computed (for speed reasons). Therefore it is best to set any non-clock
|
||||
|
|
|
@ -684,6 +684,16 @@ Summary:
|
|||
If the design is not to be completely Verilated see also the
|
||||
:vlopt:`--bbox-sys` and :vlopt:`--bbox-unsup` options.
|
||||
|
||||
.. option:: --main
|
||||
|
||||
Generates a simple main C++ file. Without :vlopt:`--timing`, you need to
|
||||
modify this file to provide some stimuli to the design. However, this option
|
||||
is especially useful with :vlopt:`--timing` and delay-generated clocks, as
|
||||
then the main file provides a timing-enabled eval loop and requires no
|
||||
modification by the user. :vlopt:`--build` can then be used to build the
|
||||
simulation, allowing you to use Verilator without directly invoking
|
||||
the C++ toolchain.
|
||||
|
||||
.. option:: --make <build-tool>
|
||||
|
||||
Generates a script for the specified build tool.
|
||||
|
@ -1164,6 +1174,16 @@ Summary:
|
|||
module. As "1fs" is the finest time precision it may be desirable to
|
||||
always use a precision of "1fs".
|
||||
|
||||
.. option:: --timing
|
||||
|
||||
.. option:: --no-timing
|
||||
|
||||
Enables/disables support for timing constructs such as delays, event
|
||||
controls (unless it's at the top of a process), wait statements, and joins.
|
||||
When disabled, timing control constructs are ignored the same way as they
|
||||
were in earlier versions of Verilator. Enabling this feature requires a C++
|
||||
compiler with coroutine support (GCC 10, Clang 5, or newer).
|
||||
|
||||
.. option:: --top <topname>
|
||||
|
||||
.. option:: --top-module <topname>
|
||||
|
@ -1747,6 +1767,19 @@ The grammar of configuration commands is as follows:
|
|||
|
||||
Same as :option:`/*verilator&32;split_var*/` metacomment.
|
||||
|
||||
.. option:: timing_on [-file "<filename>" [-lines <line> [ - <line>]]]
|
||||
|
||||
.. option:: timing_off [-file "<filename>" [-lines <line> [ - <line>]]]
|
||||
|
||||
Enables/disables timing constructs for the specified file and lines.
|
||||
When disabled, all timing control constructs in the specified source
|
||||
code locations are ignored the same way as with the
|
||||
:option:`--no-timing`, and code:`fork`/:code:`join*` blocks are
|
||||
converted into :code:`begin`/:code:`end` blocks.
|
||||
|
||||
Same as :option:`/*verilator&32;timing_on*/`,
|
||||
:option:`/*verilator&32;timing_off*/` metacomments.
|
||||
|
||||
.. option:: tracing_on [-file "<filename>" [-lines <line> [ - <line> ]]]
|
||||
|
||||
.. option:: tracing_off [-file "<filename>" [-lines <line> [ - <line> ]]]
|
||||
|
|
|
@ -505,6 +505,22 @@ or "`ifdef`"'s may break other tools.
|
|||
Verilator) text that should be passed through to the XML output as a tag,
|
||||
for use by downstream applications.
|
||||
|
||||
.. option:: /*verilator&32;timing_off*/
|
||||
|
||||
Ignore all timing constructs after this metacomment. All timing controls
|
||||
behave as if they were not there (the same way as with
|
||||
:option:`--no-timing`), and :code:`fork`/:code:`join*` blocks are
|
||||
converted into :code:`begin`/:code:`end` blocks.
|
||||
|
||||
Same as :option:`timing_off` configuration file option.
|
||||
|
||||
.. option:: /*verilator&32;timing_on*/
|
||||
|
||||
Re-enable all timing constructs after this metacomment (only applicable
|
||||
after :option:`timing_off`).
|
||||
|
||||
Same as :option:`timing_on` configuration file option.
|
||||
|
||||
.. option:: /*verilator&32;trace_init_task*/
|
||||
|
||||
Attached to a DPI import to indicate that function should be called when
|
||||
|
|
|
@ -95,6 +95,72 @@ keywords on case statement, as well as "unique" on if statements. However,
|
|||
"priority if" is currently ignored.
|
||||
|
||||
|
||||
Time
|
||||
====
|
||||
|
||||
With :vlopt:`--timing`, all timing controls are supported:
|
||||
|
||||
* delay statements,
|
||||
* event control statements not only at the top of a process,
|
||||
* intra-assignment timing controls,
|
||||
* net delays,
|
||||
* :code:`wait` statements,
|
||||
|
||||
as well as all flavors of :code:`fork`.
|
||||
|
||||
Compiling a verilated design that makes use of these features requires a
|
||||
compiler with C++20 coroutine support, e.g. Clang 5, GCC 10, or newer.
|
||||
|
||||
:code:`#0` delays cause Verilator to issue the :option:`ZERODLY` warning, as
|
||||
they work differently than described in the LRM. They do not schedule process
|
||||
resumption in the Inactive region, though the process will get resumed in the
|
||||
same time slot.
|
||||
|
||||
Rising/falling/turn-off delays are currently unsupported and cause the
|
||||
:option:`RISEFALLDLY` warning.
|
||||
|
||||
Minimum/typical/maximum delays are currently unsupported. The typical delay is
|
||||
always the one chosen. Such expressions cause the :option:`MINTYPMAX` warning.
|
||||
|
||||
Another consequence of using :vlopt:`--timing` is that the :vlopt:`--main`
|
||||
option generates a main file with a proper timing eval loop, eliminating the
|
||||
need for writing any driving C++ code. You can then simply compile the
|
||||
simulation (perhaps using :vlopt:`--build`) and run it.
|
||||
|
||||
With :vlopt:`--no-timing`, all timing controls cause the :option:`NOTIMING`
|
||||
error, with the exception of:
|
||||
|
||||
* delay statements – they are ignored (as they are in synthesis), though they
|
||||
do issue a :option:`STMTDLY` warning,
|
||||
* intra-assignment timing controls – they are ignored, though they do issue an
|
||||
:option:`ASSIGNDLY` warning,
|
||||
* net delays – they are ignored,
|
||||
* event controls at the top of the procedure,
|
||||
|
||||
Forks cause this error as well, with the exception of:
|
||||
|
||||
* forks with no statements,
|
||||
* :code:`fork..join` or :code:`fork..join_any` with one statement,
|
||||
* forks with :vlopt:`--bbox-unsup`.
|
||||
|
||||
If neither :vlopt:`--timing` nor :vlopt:`--no-timing` is specified, all timing
|
||||
controls cause the :option:`NEEDTIMINGOPT` error, with the exception of event
|
||||
controls at the top of the process. Forks cause this error as well, with the
|
||||
exception of:
|
||||
|
||||
* forks with no statements,
|
||||
* :code:`fork..join` or :code:`fork..join_any` with one statement,
|
||||
* forks with :vlopt:`--bbox-unsup`.
|
||||
|
||||
Timing controls and forks can also be ignored in specific files or parts of
|
||||
files. The :option:`/*verilator&32;timing_off*/` and
|
||||
:option:`/*verilator&32;timing_off*/` metacomments will make Verilator ignore
|
||||
the encompassed timing controls and forks, regardless of the chosen
|
||||
:vlopt:`--timing` or :vlopt:`--no-timing` option. This can also be achieved
|
||||
using the :option:`timing_off` and :option:`timing_off` options in Verilator
|
||||
configuration files.
|
||||
|
||||
|
||||
.. _Language Limitations:
|
||||
|
||||
Language Limitations
|
||||
|
@ -180,12 +246,6 @@ structure from blocking, and another from non-blocking assignments is
|
|||
unsupported.
|
||||
|
||||
|
||||
Time
|
||||
----
|
||||
|
||||
All delays (#) are ignored, as they are in synthesis.
|
||||
|
||||
|
||||
.. _Unknown States:
|
||||
|
||||
Unknown States
|
||||
|
|
|
@ -112,6 +112,8 @@ List Of Warnings
|
|||
simulators, however at one point this was a common style so disabled by
|
||||
default as a code style warning.
|
||||
|
||||
This warning is issued only if Verilator is run with :vlopt:`--no-timing`.
|
||||
|
||||
|
||||
.. option:: ASSIGNIN
|
||||
|
||||
|
@ -503,9 +505,10 @@ List Of Warnings
|
|||
constructs (e.g. always_ff and always_latch).
|
||||
|
||||
Another way DIDNOTCONVERGE may occur is if # delays are used to generate
|
||||
clocks. Verilator ignores the delays and gives an :option:`ASSIGNDLY`
|
||||
or :option:`STMTDLY` warning. If these were suppressed, due to the
|
||||
absence of the delay, the code may now oscillate.
|
||||
clocks if Verilator is run with :vlopt:`--no-timing`. In this mode,
|
||||
Verilator ignores the delays and gives an :option:`ASSIGNDLY` or
|
||||
:option:`STMTDLY` warning. If these were suppressed, due to the absence of
|
||||
the delay, the code may now oscillate.
|
||||
|
||||
Finally, rare, more difficult cases can be debugged like a C++ program;
|
||||
either enter :command:`gdb` and use its tracing facilities, or edit the
|
||||
|
@ -700,9 +703,9 @@ List Of Warnings
|
|||
Warns that a while or for statement has a condition that is always true.
|
||||
and thus results in an infinite loop if the statement ever executes.
|
||||
|
||||
This might be unintended behavior if the loop body contains statements
|
||||
that in other simulators would make time pass, which Verilator is
|
||||
ignoring due to e.g. ``STMTDLY`` warnings being disabled.
|
||||
This might be unintended behavior if Verilator is run with
|
||||
:vlopt:`--no-timing` and the loop body contains statements that would make
|
||||
time pass otherwise.
|
||||
|
||||
Ignoring this warning will only suppress the lint check, it will
|
||||
simulate correctly (i.e. hang due to the infinite loop).
|
||||
|
@ -763,6 +766,16 @@ List Of Warnings
|
|||
simulate correctly.
|
||||
|
||||
|
||||
.. option:: MINTYPMAX
|
||||
|
||||
.. code-block:: sv
|
||||
|
||||
#(3:5:8) clk = ~clk;
|
||||
|
||||
Warns that minimum, typical, and maximum delay expressions are currently
|
||||
unsupported. Only the typical delay value is used by Verilator.
|
||||
|
||||
|
||||
.. option:: MODDUP
|
||||
|
||||
.. TODO better example
|
||||
|
@ -833,6 +846,13 @@ List Of Warnings
|
|||
input.
|
||||
|
||||
|
||||
.. option:: NEEDTIMINGOPT
|
||||
|
||||
Error when a timing-related construct, such as an event control or delay,
|
||||
has been encountered, without specifying how Verilator should handle it
|
||||
(neither :vlopt:`--timing` nor :vlopt:`--no-timing` option was provided).
|
||||
|
||||
|
||||
.. option:: NOLATCH
|
||||
|
||||
.. TODO better example
|
||||
|
@ -845,6 +865,13 @@ List Of Warnings
|
|||
simulate correctly.
|
||||
|
||||
|
||||
.. option:: NOTIMING
|
||||
|
||||
Error when a timing-related construct that requires :vlopt:`--timing` has
|
||||
been encountered. Issued only if Verilator is run with the
|
||||
:vlopt:`--no-timing` option.
|
||||
|
||||
|
||||
.. option:: NULLPORT
|
||||
|
||||
Warns that a null port was detected in the module definition port
|
||||
|
@ -1082,6 +1109,16 @@ List Of Warnings
|
|||
"Duplicate macro arguments with name".
|
||||
|
||||
|
||||
.. option:: RISEFALLDLY
|
||||
|
||||
.. code-block:: sv
|
||||
|
||||
and #(1,2,3) AND (out, a, b);
|
||||
|
||||
Warns that rising, falling, and turn-off delays are currently unsupported.
|
||||
The first (rising) delay is used for all cases.
|
||||
|
||||
|
||||
.. option:: SELRANGE
|
||||
|
||||
Warns that a selection index will go out of bounds.
|
||||
|
@ -1181,11 +1218,11 @@ List Of Warnings
|
|||
|
||||
.. include:: ../../docs/gen/ex_STMTDLY_msg.rst
|
||||
|
||||
This is a warning because Verilator does not support delayed statements.
|
||||
It will ignore all such delays. In many cases ignoring a delay might be
|
||||
harmless, but if the delayed statement is, as in this example, used to
|
||||
cause some important action at a later time, it might be an important
|
||||
difference.
|
||||
This warning is issued only if Verilator is run with :vlopt:`--no-timing`.
|
||||
All delays on statements are ignored in this mode. In many cases ignoring a
|
||||
delay might be harmless, but if the delayed statement is, as in this
|
||||
example, used to cause some important action at a later time, it might be an
|
||||
important difference.
|
||||
|
||||
Some possible workarounds:
|
||||
|
||||
|
@ -1195,6 +1232,8 @@ List Of Warnings
|
|||
* Convert the statement into a FSM, or other statement that tests
|
||||
against $time.
|
||||
|
||||
* Run Verilator with :vlopt:`--timing`.
|
||||
|
||||
|
||||
.. option:: SYMRSVDWORD
|
||||
|
||||
|
@ -1576,6 +1615,16 @@ List Of Warnings
|
|||
To resolve, rename the variable to a unique name.
|
||||
|
||||
|
||||
.. option:: WAITCONST
|
||||
|
||||
.. code-block:: sv
|
||||
|
||||
wait(0); // Blocks forever
|
||||
|
||||
Warns that a `wait` statement awaits a constant condition, which means it
|
||||
either blocks forever or never blocks.
|
||||
|
||||
|
||||
.. option:: WIDTH
|
||||
|
||||
Warns that based on width rules of Verilog:
|
||||
|
@ -1645,6 +1694,14 @@ List Of Warnings
|
|||
width to the parameter usage (:code:`{PAR[31:0], PAR[31:0]}`).
|
||||
|
||||
|
||||
.. option:: ZERODLY
|
||||
|
||||
Warns that `#0` delays do not schedule the process to be resumed in the
|
||||
Inactive region. Such processes do get resumed in the same time slot
|
||||
somewhere in the Active region. Issued only if Verilator is run with the
|
||||
:vlopt:`--timing` option.
|
||||
|
||||
|
||||
Historical Warnings
|
||||
===================
|
||||
|
||||
|
|
|
@ -504,6 +504,189 @@ the top level `_eval` function, which on the high level has the form:
|
|||
}
|
||||
|
||||
|
||||
Timing
|
||||
------
|
||||
|
||||
Timing support in Verilator utilizes C++ coroutines, which is a new feature in
|
||||
C++20. The basic idea is to represent processes and tasks that await a certain
|
||||
event or simulation time as coroutines. These coroutines get suspended at the
|
||||
await, and resumed whenever the triggering event occurs, or at the expected
|
||||
simulation time.
|
||||
|
||||
There are several runtime classes used for managing such coroutines defined in
|
||||
``verilated_timing.h`` and ``verilated_timing.cpp``.
|
||||
|
||||
``VlCoroutineHandle``
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A thin wrapper around an ``std::coroutine_handle<>``. It forces move semantics,
|
||||
destroys the coroutine if it remains suspended at the end of the design's
|
||||
lifetime, and prevents multiple ``resume`` calls in the case of
|
||||
``fork..join_any``.
|
||||
|
||||
``VlCoroutine``
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Return value of all coroutines. Together with the promise type contained
|
||||
within, it allows for chaining coroutines – resuming coroutines from up the
|
||||
call stack. The calling coroutine's handle is saved in the promise object as a
|
||||
continuation, that is, the coroutine that must be resumed after the promise's
|
||||
coroutine finishes. This is necessary as C++ coroutines are stackless, meaning
|
||||
each one is suspended independently of others in the call graph.
|
||||
|
||||
``VlDelayScheduler``
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This class manages processes suspended by delays. There is one instance of this
|
||||
class per design. Coroutines ``co_await`` this object's ``delay`` function.
|
||||
Internally, they are stored in a heap structure sorted by simulation time in
|
||||
ascending order. When ``resume`` is called on the delay scheduler, all
|
||||
coroutines awaiting the current simulation time are resumed. The current
|
||||
simulation time is retrieved from a ``VerilatedContext`` object.
|
||||
|
||||
``VlTriggerScheduler``
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This class manages processes that await events (triggers). There is one such
|
||||
object per each trigger awaited by coroutines. Coroutines ``co_await`` this
|
||||
object's ``trigger`` function. They are stored in two stages – `uncommitted`
|
||||
and `ready`. First, they land in the `uncommitted` stage, and cannot be
|
||||
resumed. The ``resume`` function resumes all coroutines from the `ready` stage
|
||||
and moves `uncommitted` coroutines into `ready`. The ``commit`` function only
|
||||
moves `uncommitted` coroutines into `ready`.
|
||||
|
||||
This split is done to avoid self-triggering and triggering coroutines multiple
|
||||
times. See the `Scheduling with timing` section for details on how this is
|
||||
used.
|
||||
|
||||
``VlForkSync``
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Used for synchronizing ``fork..join`` and ``fork..join_any``. Forking
|
||||
coroutines ``co_await`` its ``join`` function, and forked ones call ``done``
|
||||
when they're finished. Once the required number of coroutines (set using
|
||||
``setCounter``) finish execution, the forking coroutine is resumed.
|
||||
|
||||
Awaitable utilities
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are also two small utility awaitable types:
|
||||
|
||||
* ``VlNow`` is an awaitable that suspends and immediately resumes coroutines.
|
||||
It is used for forcing a coroutine to be moved onto the heap. See the `Forks`
|
||||
section for more detail.
|
||||
* ``VlForever`` is used for blocking a coroutine forever. See the `Timing pass`
|
||||
section for more detail.
|
||||
|
||||
Timing pass
|
||||
^^^^^^^^^^^
|
||||
|
||||
The visitor in ``V3Timing.cpp`` transforms each timing control into a ``co_await``.
|
||||
|
||||
* event controls are turned into ``co_await`` on a trigger scheduler's
|
||||
``trigger`` method. The awaited trigger scheduler is the one corresponding to
|
||||
the sentree referenced by the event control. This sentree is also referenced
|
||||
by the ``AstCAwait`` node, to be used later by the static scheduling code.
|
||||
* delays are turned into ``co_await`` on a delay scheduler's ``delay`` method.
|
||||
The created ``AstCAwait`` nodes also reference a special sentree related to
|
||||
delays, to be used later by the static scheduling code.
|
||||
* ``join`` and ``join_any`` are turned into ``co_await`` on a ``VlForkSync``'s
|
||||
``join`` method. Each forked process gets a ``VlForkSync::done`` call at the
|
||||
end.
|
||||
|
||||
Assignments with intra-assignment timing controls are simplified into
|
||||
assignments after those timing controls, with the LHS and RHS values evaluated
|
||||
before them and stored in temporary variables.
|
||||
|
||||
``wait`` statements are transformed into while loops that check the condition
|
||||
and then await changes in variables used in the condition. If the condition is
|
||||
always false, the ``wait`` statement is replaced by a ``co_await`` on a
|
||||
``VlForever``. This is done instead of a return in case the ``wait`` is deep in
|
||||
a call stack (otherwise the coroutine's caller would continue execution).
|
||||
|
||||
Each sub-statement of a ``fork`` is put in an ``AstBegin`` node for easier
|
||||
grouping. In a later step, each of these gets transformed into a new, separate
|
||||
function. See the `Forks` section for more detail.
|
||||
|
||||
Processes that use awaits are marked as suspendable. Later, during ``V3Sched``,
|
||||
they are transformed into coroutines. Functions that use awaits get the return
|
||||
type of ``VlCoroutine``. This immediately makes them coroutines. Note that if a
|
||||
process calls a function that is a coroutine, the call gets wrapped in an
|
||||
await, which means the process itself will be marked as suspendable. A virtual
|
||||
function is a coroutine if any of its overriding or overridden functions are
|
||||
coroutines. The visitor keeps a dependency graph of functions and processes to
|
||||
handle such cases.
|
||||
|
||||
Scheduling with timing
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Timing features in Verilator are built on top of the static scheduler. Triggers
|
||||
are used for determining which delay or trigger schedulers should resume. A
|
||||
special trigger is used for the delay scheduler. This trigger is set if there
|
||||
are any coroutines awaiting the current simulation time
|
||||
(``VlDelayScheduler::awaitingCurrentTime()``).
|
||||
|
||||
All triggers used by a suspendable process are mapped to variables written in
|
||||
that process. When ordering code using ``V3Order``, these triggers are provided
|
||||
as external domains of these variables. This ensures that the necessary
|
||||
combinational logic is triggered after a coroutine resumption.
|
||||
|
||||
There are two functions for managing timing logic called by ``_eval()``:
|
||||
|
||||
* ``_timing_commit()``, which commits all coroutines whose triggers were not set
|
||||
in the current iteration,
|
||||
* ``_timing_resume()``, which calls `resume()` on all trigger and delay
|
||||
schedulers whose triggers were set in the current iteration.
|
||||
|
||||
Thanks to this separation, a coroutine awaiting a trigger cannot be suspended
|
||||
and resumed in the same iteration, and it cannot be resumed before it suspends.
|
||||
|
||||
All coroutines are committed and resumed in the 'act' eval loop. With timing
|
||||
features enabled, the ``_eval()`` function takes this form:
|
||||
|
||||
::
|
||||
void _eval() {
|
||||
while (true) {
|
||||
_eval__triggers__ico();
|
||||
if (!ico_triggers.any()) break;
|
||||
_eval_ico();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
while (true) {
|
||||
_eval__triggers__act();
|
||||
|
||||
// Commit all non-triggered coroutines
|
||||
_timing_commit();
|
||||
|
||||
if (!act_triggers.any()) break;
|
||||
latch_act_triggers_for_nba();
|
||||
|
||||
// Resume all triggered coroutines
|
||||
_timing_resume();
|
||||
|
||||
_eval_act();
|
||||
}
|
||||
if (!nba_triggers.any()) break;
|
||||
_eval_nba();
|
||||
}
|
||||
}
|
||||
|
||||
Forks
|
||||
^^^^^
|
||||
|
||||
After the scheduling step, forks sub-statements are transformed into separate
|
||||
functions, and these functions are called in place of the sub-statements. These
|
||||
calls must be without ``co_await``, so that suspension of a forked process
|
||||
doesn't suspend the forking process.
|
||||
|
||||
In forked processes, references to local variables are only allowed in
|
||||
``fork..join``, as this is the only case that ensures the lifetime of these
|
||||
locals is at least as long as the execution of the forked processes. This is
|
||||
where ``VlNow`` is used, to ensure the locals are moved to the heap before they
|
||||
are passed by reference to the forked processes.
|
||||
|
||||
|
||||
Multithreaded Mode
|
||||
------------------
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ CFG_CXXFLAGS_STD_NEWEST = @CFG_CXXFLAGS_STD_NEWEST@
|
|||
CFG_CXXFLAGS_NO_UNUSED = @CFG_CXXFLAGS_NO_UNUSED@
|
||||
# Compiler flags that turn on extra warnings
|
||||
CFG_CXXFLAGS_WEXTRA = @CFG_CXXFLAGS_WEXTRA@
|
||||
# Compiler flags that enable coroutine support
|
||||
CFG_CXXFLAGS_COROUTINES = @CFG_CXXFLAGS_COROUTINES@
|
||||
# Linker libraries for multithreading
|
||||
CFG_LDLIBS_THREADS = @CFG_LDLIBS_THREADS@
|
||||
|
||||
|
@ -142,6 +144,12 @@ ifneq ($(VM_THREADS),0)
|
|||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(VM_TIMING),0)
|
||||
ifneq ($(VM_TIMING),)
|
||||
CPPFLAGS += $(CFG_CXXFLAGS_COROUTINES)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(VK_C11),0)
|
||||
ifneq ($(VK_C11),)
|
||||
# Need C++11 at least, so always default to newest
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
// Copyright 2022 by Wilson Snyder. This program is free software; you can
|
||||
// redistribute it and/or modify it under the terms of either the GNU Lesser
|
||||
// General Public License Version 3 or the Perl Artistic License Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//=========================================================================
|
||||
///
|
||||
/// \file
|
||||
/// \brief Verilated timing implementation code
|
||||
///
|
||||
/// This file must be compiled and linked against all Verilated objects
|
||||
/// that use timing features.
|
||||
///
|
||||
/// See the internals documentation docs/internals.rst for details.
|
||||
///
|
||||
//=========================================================================
|
||||
|
||||
#include "verilated_timing.h"
|
||||
|
||||
//======================================================================
|
||||
// VlCoroutineHandle:: Methods
|
||||
|
||||
void VlCoroutineHandle::resume() {
|
||||
// Only null if we have a fork..join_any and one of the other child processes resumed the
|
||||
// main process
|
||||
if (VL_LIKELY(m_coro)) {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Resuming: "); dump(););
|
||||
m_coro();
|
||||
m_coro = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VL_DEBUG
|
||||
void VlCoroutineHandle::dump() {
|
||||
VL_DEBUG_IF(VL_PRINTF("Process waiting at %s:%d\n", m_filename, m_linenum););
|
||||
}
|
||||
#endif
|
||||
|
||||
//======================================================================
|
||||
// VlDelayScheduler:: Methods
|
||||
|
||||
#ifdef VL_DEBUG
|
||||
void VlDelayScheduler::VlDelayedCoroutine::dump() {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Awaiting time %lu: ", m_timestep););
|
||||
m_handle.dump();
|
||||
}
|
||||
#endif
|
||||
|
||||
void VlDelayScheduler::resume() {
|
||||
#ifdef VL_DEBUG
|
||||
dump();
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Resuming delayed processes\n"););
|
||||
#endif
|
||||
while (awaitingCurrentTime()) {
|
||||
if (m_queue.front().m_timestep != m_context.time()) {
|
||||
VL_FATAL_MT(__FILE__, __LINE__, "",
|
||||
"%Error: Encountered process that should've been resumed at an "
|
||||
"earlier simulation time. Missed a time slot?");
|
||||
}
|
||||
// Move max element in the heap to the end
|
||||
std::pop_heap(m_queue.begin(), m_queue.end());
|
||||
VlCoroutineHandle handle = std::move(m_queue.back().m_handle);
|
||||
m_queue.pop_back();
|
||||
handle.resume();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t VlDelayScheduler::nextTimeSlot() {
|
||||
if (empty()) {
|
||||
VL_FATAL_MT(__FILE__, __LINE__, "", "%Error: There is no next time slot scheduled");
|
||||
}
|
||||
return m_queue.front().m_timestep;
|
||||
}
|
||||
|
||||
#ifdef VL_DEBUG
|
||||
void VlDelayScheduler::dump() {
|
||||
if (m_queue.empty()) {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" No delayed processes:\n"););
|
||||
} else {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Delayed processes:\n"););
|
||||
for (auto& susp : m_queue) susp.dump();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//======================================================================
|
||||
// VlTriggerScheduler:: Methods
|
||||
|
||||
void VlTriggerScheduler::resume(const char* eventDescription) {
|
||||
#ifdef VL_DEBUG
|
||||
dump(eventDescription);
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Resuming processes waiting for %s\n", eventDescription););
|
||||
#endif
|
||||
for (auto& susp : m_ready) susp.resume();
|
||||
m_ready.clear();
|
||||
commit(eventDescription);
|
||||
}
|
||||
|
||||
void VlTriggerScheduler::commit(const char* eventDescription) {
|
||||
#ifdef VL_DEBUG
|
||||
if (!m_uncommitted.empty()) {
|
||||
VL_DEBUG_IF(
|
||||
VL_DBG_MSGF(" Committing processes waiting for %s:\n", eventDescription);
|
||||
for (auto& susp
|
||||
: m_uncommitted) {
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
m_ready.reserve(m_ready.size() + m_uncommitted.size());
|
||||
m_ready.insert(m_ready.end(), std::make_move_iterator(m_uncommitted.begin()),
|
||||
std::make_move_iterator(m_uncommitted.end()));
|
||||
m_uncommitted.clear();
|
||||
}
|
||||
|
||||
#ifdef VL_DEBUG
|
||||
void VlTriggerScheduler::dump(const char* eventDescription) {
|
||||
if (m_ready.empty()) {
|
||||
VL_DEBUG_IF(
|
||||
VL_DBG_MSGF(" No ready processes waiting for %s\n", eventDescription););
|
||||
} else {
|
||||
VL_DEBUG_IF(for (auto& susp
|
||||
: m_ready) {
|
||||
VL_DBG_MSGF(" Ready processes waiting for %s:\n", eventDescription);
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
});
|
||||
}
|
||||
if (!m_uncommitted.empty()) {
|
||||
VL_DEBUG_IF(
|
||||
VL_DBG_MSGF(" Uncommitted processes waiting for %s:\n", eventDescription);
|
||||
for (auto& susp
|
||||
: m_uncommitted) {
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//======================================================================
|
||||
// VlForkSync:: Methods
|
||||
|
||||
void VlForkSync::done(const char* filename, int linenum) {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Process forked at %s:%d finished", filename, linenum););
|
||||
if (m_join->m_counter > 0) m_join->m_counter--;
|
||||
if (m_join->m_counter == 0) m_join->m_susp.resume();
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
// VlCoroutine:: Methods
|
||||
|
||||
VlCoroutine::VlPromise::~VlPromise() {
|
||||
// Indicate to the return object that the coroutine has finished or been destroyed
|
||||
if (m_corop) m_corop->m_promisep = nullptr;
|
||||
// If there is a continuation, destroy it
|
||||
if (m_continuation) m_continuation.destroy();
|
||||
}
|
||||
|
||||
std::suspend_never VlCoroutine::VlPromise::final_suspend() noexcept {
|
||||
// Indicate to the return object that the coroutine has finished
|
||||
if (m_corop) {
|
||||
m_corop->m_promisep = nullptr;
|
||||
// Forget the return value, we won't need it and it won't be able to let us know if
|
||||
// it's destroyed
|
||||
m_corop = nullptr;
|
||||
}
|
||||
// If there is a continuation, resume it
|
||||
if (m_continuation) {
|
||||
m_continuation();
|
||||
m_continuation = nullptr;
|
||||
}
|
||||
return {};
|
||||
}
|
|
@ -0,0 +1,354 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
// Copyright 2022 by Wilson Snyder. This program is free software; you can
|
||||
// redistribute it and/or modify it under the terms of either the GNU Lesser
|
||||
// General Public License Version 3 or the Perl Artistic License Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
///
|
||||
/// \file
|
||||
/// \brief Verilated timing header
|
||||
///
|
||||
/// This file is included automatically by Verilator in some of the C++ files
|
||||
/// it generates if timing features are used.
|
||||
///
|
||||
/// This file is not part of the Verilated public-facing API.
|
||||
/// It is only for internal use.
|
||||
///
|
||||
/// See the internals documentation docs/internals.rst for details.
|
||||
///
|
||||
//*************************************************************************
|
||||
#ifndef VERILATOR_VERILATED_TIMING_H_
|
||||
#define VERILATOR_VERILATED_TIMING_H_
|
||||
|
||||
#include "verilated.h"
|
||||
|
||||
// clang-format off
|
||||
// Some preprocessor magic to support both Clang and GCC coroutines with both libc++ and libstdc++
|
||||
#ifdef __clang__
|
||||
# if __clang_major__ < 14
|
||||
# ifdef __GLIBCXX__ // Using stdlibc++
|
||||
# define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++
|
||||
# include <coroutine>
|
||||
namespace std { // Bring coroutine library into std::experimental, as Clang < 14 expects it to be there
|
||||
namespace experimental {
|
||||
using namespace std;
|
||||
}
|
||||
}
|
||||
# else // Using libc++
|
||||
# include <experimental/coroutine> // Clang older than 14, coroutines are under experimental
|
||||
namespace std {
|
||||
using namespace experimental; // Bring std::experimental into the std namespace
|
||||
}
|
||||
# endif
|
||||
# else // Clang >= 14
|
||||
# if __GLIBCXX__ // Using stdlibc++
|
||||
# define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++
|
||||
# endif
|
||||
# include <coroutine>
|
||||
# endif
|
||||
#else // Not Clang
|
||||
# include <coroutine>
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
//=============================================================================
|
||||
// VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is
|
||||
// cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got
|
||||
// suspended, another VlCoroutineHandle was created to manage it.
|
||||
|
||||
class VlCoroutineHandle final {
|
||||
VL_UNCOPYABLE(VlCoroutineHandle);
|
||||
|
||||
// MEMBERS
|
||||
std::coroutine_handle<> m_coro; // The wrapped coroutine handle
|
||||
#ifdef VL_DEBUG
|
||||
const char* m_filename;
|
||||
int m_linenum;
|
||||
#endif
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
// Construct
|
||||
VlCoroutineHandle(std::coroutine_handle<> coro = nullptr, const char* filename = nullptr,
|
||||
int linenum = 0)
|
||||
: m_coro {
|
||||
coro
|
||||
}
|
||||
#ifdef VL_DEBUG
|
||||
, m_filename{filename}, m_linenum { linenum }
|
||||
#endif
|
||||
{}
|
||||
// Move the handle, leaving a nullptr
|
||||
VlCoroutineHandle(VlCoroutineHandle&& moved)
|
||||
: m_coro {
|
||||
std::exchange(moved.m_coro, nullptr)
|
||||
}
|
||||
#ifdef VL_DEBUG
|
||||
, m_filename{moved.m_filename}, m_linenum { moved.m_linenum }
|
||||
#endif
|
||||
{}
|
||||
// Destroy if the handle isn't null
|
||||
~VlCoroutineHandle() {
|
||||
// Usually these coroutines should get resumed; we only need to clean up if we destroy a
|
||||
// model with some coroutines suspended
|
||||
if (VL_UNLIKELY(m_coro)) m_coro.destroy();
|
||||
}
|
||||
// METHODS
|
||||
// Move the handle, leaving a null handle
|
||||
auto& operator=(VlCoroutineHandle&& moved) {
|
||||
m_coro = std::exchange(moved.m_coro, nullptr);
|
||||
return *this;
|
||||
}
|
||||
// Resume the coroutine if the handle isn't null
|
||||
void resume();
|
||||
#ifdef VL_DEBUG
|
||||
void dump();
|
||||
#endif
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlDelayScheduler stores coroutines to be resumed at a certain simulation time. If the current
|
||||
// time is equal to a coroutine's resume time, the coroutine gets resumed.
|
||||
|
||||
class VlDelayScheduler final {
|
||||
// TYPES
|
||||
struct VlDelayedCoroutine {
|
||||
uint64_t m_timestep; // Simulation time when the coroutine should be resumed
|
||||
VlCoroutineHandle m_handle; // The suspended coroutine to be resumed
|
||||
|
||||
// Comparison operator for std::push_heap(), std::pop_heap()
|
||||
bool operator<(const VlDelayedCoroutine& other) const {
|
||||
return m_timestep > other.m_timestep;
|
||||
}
|
||||
#ifdef VL_DEBUG
|
||||
void dump();
|
||||
#endif
|
||||
};
|
||||
using VlDelayedCoroutineQueue = std::vector<VlDelayedCoroutine>;
|
||||
|
||||
// MEMBERS
|
||||
VerilatedContext& m_context;
|
||||
VlDelayedCoroutineQueue m_queue; // Coroutines to be restored at a certain simulation time
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
VlDelayScheduler(VerilatedContext& context)
|
||||
: m_context{context} {}
|
||||
// METHODS
|
||||
// Resume coroutines waiting for the current simulation time
|
||||
void resume();
|
||||
// Returns the simulation time of the next time slot (aborts if there are no delayed
|
||||
// coroutines)
|
||||
uint64_t nextTimeSlot();
|
||||
// Are there no delayed coroutines awaiting?
|
||||
bool empty() { return m_queue.empty(); }
|
||||
// Are there coroutines to resume at the current simulation time?
|
||||
bool awaitingCurrentTime() {
|
||||
return !empty() && m_queue.front().m_timestep <= m_context.time();
|
||||
}
|
||||
#ifdef VL_DEBUG
|
||||
void dump();
|
||||
#endif
|
||||
// Used by coroutines for co_awaiting a certain simulation time
|
||||
auto delay(uint64_t delay, const char* filename, int linenum) {
|
||||
struct Awaitable {
|
||||
VlDelayedCoroutineQueue& queue;
|
||||
uint64_t delay;
|
||||
#ifdef VL_DEBUG
|
||||
const char* filename;
|
||||
int linenum;
|
||||
#endif
|
||||
bool await_ready() { return false; } // Always suspend
|
||||
void await_suspend(std::coroutine_handle<> coro) {
|
||||
#ifdef VL_DEBUG
|
||||
queue.push_back({delay, VlCoroutineHandle{coro, filename, linenum}});
|
||||
#else
|
||||
queue.push_back({delay, coro});
|
||||
#endif
|
||||
// Move last element to the proper place in the max-heap
|
||||
std::push_heap(queue.begin(), queue.end());
|
||||
}
|
||||
void await_resume() {}
|
||||
};
|
||||
#ifdef VL_DEBUG
|
||||
return Awaitable{m_queue, m_context.time() + delay, filename, linenum};
|
||||
#else
|
||||
return Awaitable{m_queue, m_context.time() + delay};
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlTriggerScheduler stores coroutines to be resumed by a trigger. It does not keep track of its
|
||||
// trigger, relying on calling code to resume when appropriate. Coroutines are kept in two stages
|
||||
// - 'uncommitted' and 'ready'. Whenever a coroutine is suspended, it lands in the 'uncommited'
|
||||
// stage. Only when commit() is called, these coroutines get moved to the 'ready' stage. That's
|
||||
// when they can be resumed. This is done to avoid resuming processes before they start waiting.
|
||||
|
||||
class VlTriggerScheduler final {
|
||||
// TYPES
|
||||
using VlCoroutineVec = std::vector<VlCoroutineHandle>;
|
||||
|
||||
// MEMBERS
|
||||
VlCoroutineVec m_uncommitted; // Coroutines suspended before commit() was called
|
||||
// (not resumable)
|
||||
VlCoroutineVec m_ready; // Coroutines that can be resumed (all coros from m_uncommitted are
|
||||
// moved here in commit())
|
||||
|
||||
public:
|
||||
// METHODS
|
||||
// Resumes all coroutines from the 'ready' stage
|
||||
void resume(const char* eventDescription);
|
||||
// Moves all coroutines from m_uncommitted to m_ready
|
||||
void commit(const char* eventDescription);
|
||||
// Are there no coroutines awaiting?
|
||||
bool empty() { return m_ready.empty() && m_uncommitted.empty(); }
|
||||
#ifdef VL_DEBUG
|
||||
void dump(const char* eventDescription);
|
||||
#endif
|
||||
// Used by coroutines for co_awaiting a certain trigger
|
||||
auto trigger(const char* eventDescription, const char* filename, int linenum) {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n",
|
||||
eventDescription, filename, linenum););
|
||||
struct Awaitable {
|
||||
VlCoroutineVec& suspended; // Coros waiting on trigger
|
||||
#ifdef VL_DEBUG
|
||||
const char* filename;
|
||||
int linenum;
|
||||
#endif
|
||||
bool await_ready() { return false; } // Always suspend
|
||||
void await_suspend(std::coroutine_handle<> coro) {
|
||||
#ifdef VL_DEBUG
|
||||
suspended.emplace_back(coro, filename, linenum);
|
||||
#else
|
||||
suspended.emplace_back(coro);
|
||||
#endif
|
||||
}
|
||||
void await_resume() {}
|
||||
};
|
||||
#ifdef VL_DEBUG
|
||||
return Awaitable{m_uncommitted, filename, linenum};
|
||||
#else
|
||||
return Awaitable{m_uncommitted};
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlNow is a helper awaitable type that always suspends, and then immediately resumes a coroutine.
|
||||
// Allows forcing the move of coroutine locals to the heap.
|
||||
|
||||
struct VlNow {
|
||||
bool await_ready() { return false; } // Always suspend
|
||||
bool await_suspend(std::coroutine_handle<>) { return false; } // Resume immediately
|
||||
void await_resume() {}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlForever is a helper awaitable type for suspending coroutines forever. Used for constant
|
||||
// wait statements.
|
||||
|
||||
struct VlForever {
|
||||
bool await_ready() { return false; } // Always suspend
|
||||
void await_suspend(std::coroutine_handle<> coro) { coro.destroy(); }
|
||||
void await_resume() {}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlForkSync is used to manage fork..join and fork..join_any constructs.
|
||||
|
||||
class VlForkSync final {
|
||||
// VlJoin stores the handle of a suspended coroutine that did a fork..join or fork..join_any.
|
||||
// If the counter reaches 0, the suspended coroutine shall be resumed.
|
||||
struct VlJoin {
|
||||
size_t m_counter = 0; // When reaches 0, resume suspended coroutine
|
||||
VlCoroutineHandle m_susp; // Coroutine to resume
|
||||
};
|
||||
|
||||
// The join info is shared among all forked processes
|
||||
std::shared_ptr<VlJoin> m_join;
|
||||
|
||||
public:
|
||||
// Create the join object and set the counter to the specified number
|
||||
void init(size_t count) { m_join.reset(new VlJoin{count, {}}); }
|
||||
// Called whenever any of the forked processes finishes. If the join counter reaches 0, the
|
||||
// main process gets resumed
|
||||
void done(const char* filename, int linenum);
|
||||
// Used by coroutines for co_awaiting a join
|
||||
auto join(const char* filename, int linenum) {
|
||||
assert(m_join);
|
||||
VL_DEBUG_IF(
|
||||
VL_DBG_MSGF(" Awaiting join of fork at: %s:%d", filename, linenum););
|
||||
struct Awaitable {
|
||||
const std::shared_ptr<VlJoin> join; // Join to await on
|
||||
bool await_ready() { return join->m_counter == 0; } // Suspend if join still exists
|
||||
void await_suspend(std::coroutine_handle<> coro) { join->m_susp = coro; }
|
||||
void await_resume() {}
|
||||
};
|
||||
return Awaitable{m_join};
|
||||
}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VlCoroutine
|
||||
// Return value of a coroutine. Used for chaining coroutine suspension/resumption.
|
||||
|
||||
class VlCoroutine final {
|
||||
private:
|
||||
// TYPES
|
||||
struct VlPromise {
|
||||
std::coroutine_handle<> m_continuation; // Coroutine to resume after this one finishes
|
||||
VlCoroutine* m_corop = nullptr; // Pointer to the coroutine return object
|
||||
|
||||
~VlPromise();
|
||||
|
||||
VlCoroutine get_return_object() { return {this}; }
|
||||
|
||||
// Never suspend at the start of the coroutine
|
||||
std::suspend_never initial_suspend() { return {}; }
|
||||
|
||||
// Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up
|
||||
// after itself)
|
||||
std::suspend_never final_suspend() noexcept;
|
||||
|
||||
void unhandled_exception() { std::abort(); }
|
||||
void return_void() const {}
|
||||
};
|
||||
|
||||
// MEMBERS
|
||||
VlPromise* m_promisep; // The promise created for this coroutine
|
||||
|
||||
public:
|
||||
// TYPES
|
||||
using promise_type = VlPromise; // promise_type has to be public
|
||||
|
||||
// CONSTRUCTORS
|
||||
// Construct
|
||||
VlCoroutine(VlPromise* p)
|
||||
: m_promisep{p} {
|
||||
m_promisep->m_corop = this;
|
||||
}
|
||||
// Move. Update the pointers each time the return object is moved
|
||||
VlCoroutine(VlCoroutine&& other)
|
||||
: m_promisep{std::exchange(other.m_promisep, nullptr)} {
|
||||
if (m_promisep) m_promisep->m_corop = this;
|
||||
}
|
||||
~VlCoroutine() {
|
||||
// Indicate to the promise that the return object is gone
|
||||
if (m_promisep) m_promisep->m_corop = nullptr;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
// Suspend the awaiter if the coroutine is suspended (the promise exists)
|
||||
bool await_ready() const noexcept { return !m_promisep; }
|
||||
// Set the awaiting coroutine as the continuation of the current coroutine
|
||||
void await_suspend(std::coroutine_handle<> coro) { m_promisep->m_continuation = coro; }
|
||||
void await_resume() const noexcept {}
|
||||
};
|
||||
|
||||
#endif // Guard
|
|
@ -133,6 +133,10 @@ public:
|
|||
void clearTriggered() { m_triggered = false; }
|
||||
};
|
||||
|
||||
inline std::string VL_TO_STRING(const VlEvent& e) {
|
||||
return std::string("triggered=") + (e.isTriggered() ? "true" : "false");
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
// Shuffle RNG
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@ RAW_OBJS = \
|
|||
V3DepthBlock.o \
|
||||
V3Descope.o \
|
||||
V3DupFinder.o \
|
||||
V3Timing.o \
|
||||
V3EmitCBase.o \
|
||||
V3EmitCConstPool.o \
|
||||
V3EmitCFunc.o \
|
||||
|
@ -241,6 +242,7 @@ RAW_OBJS = \
|
|||
V3SchedAcyclic.o \
|
||||
V3SchedPartition.o \
|
||||
V3SchedReplicate.o \
|
||||
V3SchedTiming.o \
|
||||
V3Scope.o \
|
||||
V3Scoreboard.o \
|
||||
V3Slice.o \
|
||||
|
|
|
@ -373,9 +373,11 @@ private:
|
|||
}
|
||||
|
||||
// Convert to blocking assignment
|
||||
nodep->replaceWith(new AstAssign{nodep->fileline(), //
|
||||
nodep->lhsp()->unlinkFrBack(), //
|
||||
nodep->rhsp()->unlinkFrBack()});
|
||||
nodep->replaceWith(new AstAssign{
|
||||
nodep->fileline(), //
|
||||
nodep->lhsp()->unlinkFrBack(), //
|
||||
nodep->rhsp()->unlinkFrBack(), //
|
||||
nodep->timingControlp() ? nodep->timingControlp()->unlinkFrBack() : nullptr});
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
|
||||
|
@ -473,8 +475,9 @@ private:
|
|||
}
|
||||
|
||||
AstActive* const wantactivep
|
||||
= m_clockedProcess ? m_namer.getActive(nodep->fileline(), oldsensesp)
|
||||
: m_namer.getSpecialActive<AstSenItem::Combo>(nodep->fileline());
|
||||
= !m_clockedProcess ? m_namer.getSpecialActive<AstSenItem::Combo>(nodep->fileline())
|
||||
: oldsensesp ? m_namer.getActive(nodep->fileline(), oldsensesp)
|
||||
: m_namer.getSpecialActive<AstSenItem::Initial>(nodep->fileline());
|
||||
|
||||
// Delete sensitivity list
|
||||
if (oldsensesp) VL_DO_DANGLING(oldsensesp->deleteTree(), oldsensesp);
|
||||
|
@ -495,6 +498,12 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void visitSenItems(AstNode* nodep) {
|
||||
if (v3Global.opt.timing().isSetTrue()) {
|
||||
nodep->foreach<AstSenItem>([this](AstSenItem* senItemp) { visit(senItemp); });
|
||||
}
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstScope* nodep) override {
|
||||
m_namer.main(nodep); // Clear last scope's names, and collect this scope's existing names
|
||||
|
@ -509,6 +518,7 @@ private:
|
|||
}
|
||||
virtual void visit(AstInitial* nodep) override {
|
||||
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
|
||||
visitSenItems(nodep);
|
||||
moveUnderSpecial<AstSenItem::Initial>(nodep);
|
||||
}
|
||||
virtual void visit(AstFinal* nodep) override {
|
||||
|
@ -529,6 +539,7 @@ private:
|
|||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
return;
|
||||
}
|
||||
visitSenItems(nodep);
|
||||
visitAlways(nodep, nodep->sensesp(), nodep->keyword());
|
||||
}
|
||||
virtual void visit(AstAlwaysPostponed* nodep) override {
|
||||
|
@ -540,16 +551,18 @@ private:
|
|||
virtual void visit(AstAlwaysPublic* nodep) override {
|
||||
visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS);
|
||||
}
|
||||
virtual void visit(AstCFunc* nodep) override { visitSenItems(nodep); }
|
||||
|
||||
virtual void visit(AstSenItem* nodep) override {
|
||||
UASSERT_OBJ(!m_walkingBody, nodep, "Should not reach here when walking body");
|
||||
UASSERT_OBJ(!m_walkingBody, nodep,
|
||||
"Should not reach here when walking body without --timing");
|
||||
if (!nodep->sensp()) return; // Ignore sequential items (e.g.: initial, comb, etc.)
|
||||
|
||||
m_clockedProcess = true;
|
||||
if (nodep->edgeType() != VEdgeType::ET_CHANGED) m_allChanged = false;
|
||||
|
||||
if (nodep->varrefp()) {
|
||||
if (const AstBasicDType* const basicp = nodep->varrefp()->dtypep()->basicp()) {
|
||||
if (const auto* const dtypep = nodep->sensp()->dtypep()) {
|
||||
if (const auto* const basicp = dtypep->basicp()) {
|
||||
if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT);
|
||||
}
|
||||
}
|
||||
|
@ -573,6 +586,7 @@ private:
|
|||
}
|
||||
virtual void visit(AstAssignDly* nodep) override {
|
||||
m_canBeComb = false;
|
||||
if (nodep->isTimingControl()) m_clockedProcess = true;
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstFireEvent* nodep) override {
|
||||
|
@ -587,13 +601,27 @@ private:
|
|||
m_canBeComb = false;
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
virtual void visit(AstFork* nodep) override {
|
||||
if (nodep->isTimingControl()) {
|
||||
m_canBeComb = false;
|
||||
m_clockedProcess = true;
|
||||
}
|
||||
// Do not iterate children, technically not part of this process
|
||||
}
|
||||
|
||||
//--------------------
|
||||
virtual void visit(AstVar*) override {} // Accelerate
|
||||
virtual void visit(AstVarScope*) override {} // Accelerate
|
||||
virtual void visit(AstNode* nodep) override {
|
||||
if (m_walkingBody && !m_canBeComb) return; // Accelerate
|
||||
if (!v3Global.opt.timing().isSetTrue() && m_walkingBody && !m_canBeComb) {
|
||||
return; // Accelerate
|
||||
}
|
||||
if (!nodep->isPure()) m_canBeComb = false;
|
||||
if (nodep->isTimingControl()) {
|
||||
m_canBeComb = false;
|
||||
m_clockedProcess = true;
|
||||
return;
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
|
|
84
src/V3Ast.h
84
src/V3Ast.h
|
@ -450,6 +450,9 @@ public:
|
|||
CHARPTR,
|
||||
MTASKSTATE,
|
||||
TRIGGERVEC,
|
||||
DELAY_SCHEDULER,
|
||||
TRIGGER_SCHEDULER,
|
||||
FORK_SYNC,
|
||||
// Unsigned and two state; fundamental types
|
||||
UINT32,
|
||||
UINT64,
|
||||
|
@ -460,21 +463,57 @@ public:
|
|||
};
|
||||
enum en m_e;
|
||||
const char* ascii() const {
|
||||
static const char* const names[]
|
||||
= {"%E-unk", "bit", "byte", "chandle", "event",
|
||||
"int", "integer", "logic", "longint", "real",
|
||||
"shortint", "time", "string", "VerilatedScope*", "char*",
|
||||
"VlMTaskState", "VlTriggerVec", "IData", "QData", "LOGIC_IMPLICIT",
|
||||
" MAX"};
|
||||
static const char* const names[] = {"%E-unk",
|
||||
"bit",
|
||||
"byte",
|
||||
"chandle",
|
||||
"event",
|
||||
"int",
|
||||
"integer",
|
||||
"logic",
|
||||
"longint",
|
||||
"real",
|
||||
"shortint",
|
||||
"time",
|
||||
"string",
|
||||
"VerilatedScope*",
|
||||
"char*",
|
||||
"VlMTaskState",
|
||||
"VlTriggerVec",
|
||||
"VlDelayScheduler",
|
||||
"VlTriggerScheduler",
|
||||
"VlFork",
|
||||
"IData",
|
||||
"QData",
|
||||
"LOGIC_IMPLICIT",
|
||||
" MAX"};
|
||||
return names[m_e];
|
||||
}
|
||||
const char* dpiType() const {
|
||||
static const char* const names[]
|
||||
= {"%E-unk", "svBit", "char", "void*", "char",
|
||||
"int", "%E-integer", "svLogic", "long long", "double",
|
||||
"short", "%E-time", "const char*", "dpiScope", "const char*",
|
||||
"%E-mtaskstate", "%E-triggervec", "IData", "QData", "%E-logic-implct",
|
||||
" MAX"};
|
||||
static const char* const names[] = {"%E-unk",
|
||||
"svBit",
|
||||
"char",
|
||||
"void*",
|
||||
"char",
|
||||
"int",
|
||||
"%E-integer",
|
||||
"svLogic",
|
||||
"long long",
|
||||
"double",
|
||||
"short",
|
||||
"%E-time",
|
||||
"const char*",
|
||||
"dpiScope",
|
||||
"const char*",
|
||||
"%E-mtaskstate",
|
||||
"%E-triggervec",
|
||||
"%E-dly-sched",
|
||||
"%E-trig-sched",
|
||||
"%E-fork",
|
||||
"IData",
|
||||
"QData",
|
||||
"%E-logic-implct",
|
||||
" MAX"};
|
||||
return names[m_e];
|
||||
}
|
||||
static void selfTest() {
|
||||
|
@ -508,6 +547,9 @@ public:
|
|||
case CHARPTR: return 0; // opaque
|
||||
case MTASKSTATE: return 0; // opaque
|
||||
case TRIGGERVEC: return 0; // opaque
|
||||
case DELAY_SCHEDULER: return 0; // opaque
|
||||
case TRIGGER_SCHEDULER: return 0; // opaque
|
||||
case FORK_SYNC: return 0; // opaque
|
||||
case UINT32: return 32;
|
||||
case UINT64: return 64;
|
||||
default: return 0;
|
||||
|
@ -544,7 +586,8 @@ public:
|
|||
}
|
||||
bool isOpaque() const { // IE not a simple number we can bit optimize
|
||||
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|
||||
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DOUBLE);
|
||||
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER
|
||||
|| m_e == TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == DOUBLE);
|
||||
}
|
||||
bool isDouble() const { return m_e == DOUBLE; }
|
||||
bool isEvent() const { return m_e == EVENT; }
|
||||
|
@ -1825,17 +1868,19 @@ public:
|
|||
// Changes control flow, disable some optimizations
|
||||
virtual bool isBrancher() const { return false; }
|
||||
// Else a AstTime etc that can't be pushed out
|
||||
virtual bool isGateOptimizable() const { return true; }
|
||||
virtual bool isGateOptimizable() const { return !isTimingControl(); }
|
||||
// GateDedupable is a slightly larger superset of GateOptimzable (eg, AstNodeIf)
|
||||
virtual bool isGateDedupable() const { return isGateOptimizable(); }
|
||||
// Else creates output or exits, etc, not unconsumed
|
||||
virtual bool isOutputter() const { return false; }
|
||||
// Else a AstTime etc which output can't be predicted from input
|
||||
virtual bool isPredictOptimizable() const { return true; }
|
||||
virtual bool isPredictOptimizable() const { return !isTimingControl(); }
|
||||
// Else a $display, etc, that must be ordered with other displays
|
||||
virtual bool isPure() const { return true; }
|
||||
// Else a AstTime etc that can't be substituted out
|
||||
virtual bool isSubstOptimizable() const { return true; }
|
||||
// An event control, delay, wait, etc.
|
||||
virtual bool isTimingControl() const { return false; }
|
||||
// isUnlikely handles $stop or similar statement which means an above IF
|
||||
// statement is unlikely to be taken
|
||||
virtual bool isUnlikely() const { return false; }
|
||||
|
@ -2628,6 +2673,8 @@ public:
|
|||
|
||||
class AstNodeProcedure VL_NOT_FINAL : public AstNode {
|
||||
// IEEE procedure: initial, final, always
|
||||
bool m_suspendable = false; // Is suspendable by a Delay, EventControl, etc.
|
||||
|
||||
protected:
|
||||
AstNodeProcedure(VNType t, FileLine* fl, AstNode* bodysp)
|
||||
: AstNode{t, fl} {
|
||||
|
@ -2641,6 +2688,8 @@ public:
|
|||
AstNode* bodysp() const { return op2p(); } // op2 = Statements to evaluate
|
||||
void addStmtp(AstNode* nodep) { addOp2p(nodep); }
|
||||
bool isJustOneBodyStmt() const { return bodysp() && !bodysp()->nextp(); }
|
||||
bool isSuspendable() const { return m_suspendable; }
|
||||
void setSuspendable() { m_suspendable = true; }
|
||||
};
|
||||
|
||||
class AstNodeStmt VL_NOT_FINAL : public AstNode {
|
||||
|
@ -2670,7 +2719,7 @@ protected:
|
|||
: AstNodeStmt{t, fl} {
|
||||
setOp1p(rhsp);
|
||||
setOp2p(lhsp);
|
||||
addNOp3p(timingControlp);
|
||||
setNOp3p(timingControlp);
|
||||
dtypeFrom(lhsp);
|
||||
}
|
||||
|
||||
|
@ -2683,7 +2732,7 @@ public:
|
|||
AstNode* lhsp() const { return op2p(); } // op2 = Assign to
|
||||
// op3 = Timing controls (delays, event controls)
|
||||
AstNode* timingControlp() const { return op3p(); }
|
||||
void addTimingControlp(AstNode* const np) { addNOp3p(np); }
|
||||
void timingControlp(AstNode* const np) { setNOp3p(np); }
|
||||
void rhsp(AstNode* np) { setOp1p(np); }
|
||||
void lhsp(AstNode* np) { setOp2p(np); }
|
||||
virtual bool hasDType() const override { return true; }
|
||||
|
@ -2691,6 +2740,7 @@ public:
|
|||
virtual int instrCount() const override { return widthInstrs(); }
|
||||
virtual bool same(const AstNode*) const override { return true; }
|
||||
virtual string verilogKwd() const override { return "="; }
|
||||
virtual bool isTimingControl() const override { return timingControlp(); }
|
||||
virtual bool brokeLhsMustBeLvalue() const = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -711,6 +711,12 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
|
|||
info.m_type = "VlMTaskVertex";
|
||||
} else if (bdtypep->isTriggerVec()) {
|
||||
info.m_type = "VlTriggerVec<" + cvtToStr(dtypep->width()) + ">";
|
||||
} else if (bdtypep->isDelayScheduler()) {
|
||||
info.m_type = "VlDelayScheduler";
|
||||
} else if (bdtypep->isTriggerScheduler()) {
|
||||
info.m_type = "VlTriggerScheduler";
|
||||
} else if (bdtypep->isForkSync()) {
|
||||
info.m_type = "VlForkSync";
|
||||
} else if (bdtypep->isEvent()) {
|
||||
info.m_type = "VlEvent";
|
||||
} else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width
|
||||
|
@ -1296,7 +1302,10 @@ void AstNode::dump(std::ostream& str) const {
|
|||
}
|
||||
}
|
||||
|
||||
void AstNodeProcedure::dump(std::ostream& str) const { this->AstNode::dump(str); }
|
||||
void AstNodeProcedure::dump(std::ostream& str) const {
|
||||
this->AstNode::dump(str);
|
||||
if (isSuspendable()) str << " [SUSP]";
|
||||
}
|
||||
|
||||
void AstAlways::dump(std::ostream& str) const {
|
||||
this->AstNodeProcedure::dump(str);
|
||||
|
@ -1954,8 +1963,34 @@ void AstCFunc::dump(std::ostream& str) const {
|
|||
if (isConstructor()) str << " [CTOR]";
|
||||
if (isDestructor()) str << " [DTOR]";
|
||||
if (isVirtual()) str << " [VIRT]";
|
||||
if (isCoroutine()) str << " [CORO]";
|
||||
}
|
||||
void AstCAwait::dump(std::ostream& str) const {
|
||||
this->AstNodeStmt::dump(str);
|
||||
if (sensesp()) {
|
||||
str << " => ";
|
||||
sensesp()->dump(str);
|
||||
}
|
||||
}
|
||||
void AstCUse::dump(std::ostream& str) const {
|
||||
this->AstNode::dump(str);
|
||||
str << " [" << useType() << "]";
|
||||
}
|
||||
|
||||
AstAlways* AstAssignW::convertToAlways() {
|
||||
AstNode* const lhs1p = lhsp()->unlinkFrBack();
|
||||
AstNode* const rhs1p = rhsp()->unlinkFrBack();
|
||||
AstNode* const controlp = timingControlp() ? timingControlp()->unlinkFrBack() : nullptr;
|
||||
FileLine* const flp = fileline();
|
||||
AstNode* bodysp = new AstAssign{flp, lhs1p, rhs1p, controlp};
|
||||
if (controlp) {
|
||||
// If there's a timing control, put the assignment in a fork..join_none. This process won't
|
||||
// get marked as suspendable and thus will be scheduled normally
|
||||
auto* forkp = new AstFork{flp, "", bodysp};
|
||||
forkp->joinType(VJoinType::JOIN_NONE);
|
||||
bodysp = forkp;
|
||||
}
|
||||
AstAlways* const newp = new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, bodysp};
|
||||
replaceWith(newp); // User expected to then deleteTree();
|
||||
return newp;
|
||||
}
|
||||
|
|
|
@ -1004,6 +1004,9 @@ public:
|
|||
bool isDouble() const { return keyword().isDouble(); }
|
||||
bool isEvent() const { return keyword() == VBasicDTypeKwd::EVENT; }
|
||||
bool isTriggerVec() const { return keyword() == VBasicDTypeKwd::TRIGGERVEC; }
|
||||
bool isForkSync() const { return keyword() == VBasicDTypeKwd::FORK_SYNC; }
|
||||
bool isDelayScheduler() const { return keyword() == VBasicDTypeKwd::DELAY_SCHEDULER; }
|
||||
bool isTriggerScheduler() const { return keyword() == VBasicDTypeKwd::TRIGGER_SCHEDULER; }
|
||||
bool isOpaque() const { return keyword().isOpaque(); }
|
||||
bool isString() const { return keyword().isString(); }
|
||||
bool isZeroInit() const { return keyword().isZeroInit(); }
|
||||
|
@ -1930,6 +1933,7 @@ public:
|
|||
return op1p();
|
||||
} // op1 = Extracting what (nullptr=TBD during parsing)
|
||||
AstNode* lsbp() const { return op2p(); } // op2 = Msb selection expression
|
||||
void lsbp(AstNode* const lsbp) { setOp2p(lsbp); }
|
||||
AstNode* widthp() const { return op3p(); } // op3 = Width
|
||||
int widthConst() const { return VN_AS(widthp(), Const)->toSInt(); }
|
||||
int lsbConst() const { return VN_AS(lsbp(), Const)->toSInt(); }
|
||||
|
@ -2108,6 +2112,7 @@ private:
|
|||
bool m_isLatched : 1; // Not assigned in all control paths of combo always
|
||||
bool m_isForceable : 1; // May be forced/released externally from user C code
|
||||
bool m_isWrittenByDpi : 1; // This variable can be written by a DPI Export
|
||||
bool m_isWrittenBySuspendable : 1; // This variable can be written by a suspendable process
|
||||
|
||||
void init() {
|
||||
m_ansi = false;
|
||||
|
@ -2148,6 +2153,7 @@ private:
|
|||
m_isLatched = false;
|
||||
m_isForceable = false;
|
||||
m_isWrittenByDpi = false;
|
||||
m_isWrittenBySuspendable = false;
|
||||
m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN;
|
||||
}
|
||||
|
||||
|
@ -2315,6 +2321,8 @@ public:
|
|||
void setForceable() { m_isForceable = true; }
|
||||
bool isWrittenByDpi() const { return m_isWrittenByDpi; }
|
||||
void setWrittenByDpi() { m_isWrittenByDpi = true; }
|
||||
bool isWrittenBySuspendable() const { return m_isWrittenBySuspendable; }
|
||||
void setWrittenBySuspendable() { m_isWrittenBySuspendable = true; }
|
||||
// METHODS
|
||||
virtual void name(const string& name) override { m_name = name; }
|
||||
virtual void tag(const string& text) override { m_tag = text; }
|
||||
|
@ -3468,6 +3476,7 @@ public:
|
|||
}
|
||||
// op1 = Expression sensitized, if any
|
||||
AstNode* sensp() const { return op1p(); }
|
||||
void sensp(AstNode* const nodep) { setOp1p(nodep); }
|
||||
AstNodeVarRef* varrefp() const { return VN_CAST(op1p(), NodeVarRef); }
|
||||
//
|
||||
bool isClocked() const { return edgeType().clockedStmt(); }
|
||||
|
@ -3604,7 +3613,8 @@ public:
|
|||
}
|
||||
ASTNODE_NODE_FUNCS(Assign)
|
||||
virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
|
||||
return new AstAssign(this->fileline(), lhsp, rhsp);
|
||||
AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr;
|
||||
return new AstAssign(this->fileline(), lhsp, rhsp, controlp);
|
||||
}
|
||||
virtual bool brokeLhsMustBeLvalue() const override { return true; }
|
||||
};
|
||||
|
@ -3628,7 +3638,8 @@ public:
|
|||
: ASTGEN_SUPER_AssignDly(fl, lhsp, rhsp, timingControlp) {}
|
||||
ASTNODE_NODE_FUNCS(AssignDly)
|
||||
virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
|
||||
return new AstAssignDly(this->fileline(), lhsp, rhsp);
|
||||
AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr;
|
||||
return new AstAssignDly(this->fileline(), lhsp, rhsp, controlp);
|
||||
}
|
||||
virtual bool isGateOptimizable() const override { return false; }
|
||||
virtual string verilogKwd() const override { return "<="; }
|
||||
|
@ -3638,21 +3649,15 @@ public:
|
|||
class AstAssignW final : public AstNodeAssign {
|
||||
// Like assign, but wire/assign's in verilog, the only setting of the specified variable
|
||||
public:
|
||||
AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
|
||||
: ASTGEN_SUPER_AssignW(fl, lhsp, rhsp) {}
|
||||
AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr)
|
||||
: ASTGEN_SUPER_AssignW(fl, lhsp, rhsp, timingControlp) {}
|
||||
ASTNODE_NODE_FUNCS(AssignW)
|
||||
virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
|
||||
return new AstAssignW(this->fileline(), lhsp, rhsp);
|
||||
AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr;
|
||||
return new AstAssignW(this->fileline(), lhsp, rhsp, controlp);
|
||||
}
|
||||
virtual bool brokeLhsMustBeLvalue() const override { return true; }
|
||||
AstAlways* convertToAlways() {
|
||||
AstNode* const lhs1p = lhsp()->unlinkFrBack();
|
||||
AstNode* const rhs1p = rhsp()->unlinkFrBack();
|
||||
AstAlways* const newp = new AstAlways(fileline(), VAlwaysKwd::ALWAYS, nullptr,
|
||||
new AstAssign(fileline(), lhs1p, rhs1p));
|
||||
replaceWith(newp); // User expected to then deleteTree();
|
||||
return newp;
|
||||
}
|
||||
AstAlways* convertToAlways();
|
||||
};
|
||||
|
||||
class AstAssignVarScope final : public AstNodeAssign {
|
||||
|
@ -3722,7 +3727,7 @@ public:
|
|||
}
|
||||
ASTNODE_NODE_FUNCS(FireEvent);
|
||||
AstNode* operandp() const { return op1p(); }
|
||||
bool isDeleyed() const { return m_delayed; }
|
||||
bool isDelayed() const { return m_delayed; }
|
||||
};
|
||||
|
||||
class AstAssignPre final : public AstNodeAssign {
|
||||
|
@ -3943,6 +3948,7 @@ public:
|
|||
setNOp2p(stmtsp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(Delay)
|
||||
virtual bool isTimingControl() const override { return true; }
|
||||
virtual bool same(const AstNode* /*samep*/) const override { return true; }
|
||||
//
|
||||
AstNode* lhsp() const { return op1p(); } // op1 = delay value
|
||||
|
@ -4735,8 +4741,10 @@ public:
|
|||
addNOp3p(bodysp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(Wait)
|
||||
AstNode* bodysp() const { return op3p(); } // op3 = body of loop
|
||||
AstNode* condp() const { return op2p(); } // op2 = condition
|
||||
AstNode* bodysp() const { return op3p(); } // op3 = statements after wait
|
||||
bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); }
|
||||
virtual bool isTimingControl() const override { return true; }
|
||||
};
|
||||
|
||||
class AstWhile final : public AstNodeStmt {
|
||||
|
@ -5110,6 +5118,7 @@ public:
|
|||
AstFork(FileLine* fl, const string& name, AstNode* stmtsp)
|
||||
: ASTGEN_SUPER_Fork(fl, name, stmtsp) {}
|
||||
ASTNODE_NODE_FUNCS(Fork)
|
||||
virtual bool isTimingControl() const override { return !joinType().joinNone(); }
|
||||
virtual void dump(std::ostream& str) const override;
|
||||
VJoinType joinType() const { return m_joinType; }
|
||||
void joinType(const VJoinType& flag) { m_joinType = flag; }
|
||||
|
@ -5391,21 +5400,20 @@ public:
|
|||
|
||||
class AstEventControl final : public AstNodeStmt {
|
||||
// Parents: stmtlist
|
||||
// Children: sentree, stmtlist
|
||||
public:
|
||||
AstEventControl(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp)
|
||||
: ASTGEN_SUPER_EventControl(fl) {
|
||||
setNOp1p(sensesp);
|
||||
setNOp2p(stmtsp);
|
||||
addNOp2p(stmtsp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(EventControl)
|
||||
virtual string verilogKwd() const override { return "@(%l) %r"; }
|
||||
virtual bool isGateOptimizable() const override { return false; }
|
||||
virtual bool isPredictOptimizable() const override { return false; }
|
||||
virtual bool isPure() const override { return false; }
|
||||
virtual bool isOutputter() const override { return false; }
|
||||
virtual bool isTimingControl() const override { return true; }
|
||||
virtual int instrCount() const override { return 0; }
|
||||
AstSenTree* sensesp() const { return VN_AS(op1p(), SenTree); }
|
||||
AstNode* stmtsp() const { return op2p(); }
|
||||
void stmtsp(AstNode* stmtsp) { setNOp2p(stmtsp); }
|
||||
};
|
||||
|
||||
class AstTimeFormat final : public AstNodeStmt {
|
||||
|
@ -9109,6 +9117,7 @@ public:
|
|||
AstScope* scopep() const { return m_scopep; }
|
||||
void scopep(AstScope* nodep) { m_scopep = nodep; }
|
||||
string rtnTypeVoid() const { return ((m_rtnType == "") ? "void" : m_rtnType); }
|
||||
void rtnType(const string& rtnType) { m_rtnType = rtnType; }
|
||||
bool dontCombine() const { return m_dontCombine || isTrace() || entryPoint(); }
|
||||
void dontCombine(bool flag) { m_dontCombine = flag; }
|
||||
bool dontInline() const { return dontCombine() || slow() || funcPublic(); }
|
||||
|
@ -9153,6 +9162,7 @@ public:
|
|||
void dpiImportWrapper(bool flag) { m_dpiImportWrapper = flag; }
|
||||
void dpiTraceInit(bool flag) { m_dpiTraceInit = flag; }
|
||||
bool dpiTraceInit() const { return m_dpiTraceInit; }
|
||||
bool isCoroutine() const { return m_rtnType == "VlCoroutine"; }
|
||||
//
|
||||
// If adding node accessors, see below emptyBody
|
||||
AstNode* argsp() const { return op1p(); }
|
||||
|
@ -9321,6 +9331,32 @@ public:
|
|||
VUseType useType() const { return m_useType; }
|
||||
};
|
||||
|
||||
class AstCAwait final : public AstNodeStmt {
|
||||
// Emit C++'s co_await statement
|
||||
// Children: expression
|
||||
AstSenTree* m_sensesp; // Sentree related to this await
|
||||
public:
|
||||
AstCAwait(FileLine* fl, AstNode* exprsp, AstSenTree* sensesp = nullptr)
|
||||
: ASTGEN_SUPER_CAwait(fl)
|
||||
, m_sensesp{sensesp} {
|
||||
setNOp1p(exprsp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(CAwait)
|
||||
virtual bool isTimingControl() const override { return true; }
|
||||
virtual const char* broken() const override {
|
||||
BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists());
|
||||
return nullptr;
|
||||
}
|
||||
virtual void cloneRelink() override {
|
||||
if (m_sensesp && m_sensesp->clonep()) m_sensesp = m_sensesp->clonep();
|
||||
}
|
||||
virtual void dump(std::ostream& str) const override;
|
||||
AstNode* exprp() const { return op1p(); } // op1 = awaited expression
|
||||
void exprp(AstNode* const nodep) { setNOp1p(nodep); }
|
||||
AstSenTree* sensesp() const { return m_sensesp; }
|
||||
void clearSensesp() { m_sensesp = nullptr; }
|
||||
};
|
||||
|
||||
class AstMTaskBody final : public AstNode {
|
||||
// Hold statements for each MTask
|
||||
private:
|
||||
|
@ -9475,6 +9511,7 @@ private:
|
|||
AstCFunc* m_evalp = nullptr; // The '_eval' function
|
||||
AstCFunc* m_evalNbap = nullptr; // The '_eval__nba' function
|
||||
AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable
|
||||
AstVar* m_delaySchedulerp = nullptr; // The delay scheduler variable
|
||||
AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module
|
||||
VTimescale m_timeunit; // Global time unit
|
||||
VTimescale m_timeprecision; // Global time precision
|
||||
|
@ -9492,6 +9529,7 @@ public:
|
|||
BROKEN_RTN(m_evalp && !m_evalp->brokeExists());
|
||||
BROKEN_RTN(m_dpiExportTriggerp && !m_dpiExportTriggerp->brokeExists());
|
||||
BROKEN_RTN(m_topScopep && !m_topScopep->brokeExists());
|
||||
BROKEN_RTN(m_delaySchedulerp && !m_delaySchedulerp->brokeExists());
|
||||
return nullptr;
|
||||
}
|
||||
virtual void cloneRelink() override { V3ERROR_NA; }
|
||||
|
@ -9528,6 +9566,8 @@ public:
|
|||
void evalNbap(AstCFunc* funcp) { m_evalNbap = funcp; }
|
||||
AstVarScope* dpiExportTriggerp() const { return m_dpiExportTriggerp; }
|
||||
void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; }
|
||||
AstVar* delaySchedulerp() const { return m_delaySchedulerp; }
|
||||
void delaySchedulerp(AstVar* const varScopep) { m_delaySchedulerp = varScopep; }
|
||||
AstTopScope* topScopep() const { return m_topScopep; }
|
||||
void createTopScope(AstScope* scopep) {
|
||||
UASSERT(scopep, "Must not be nullptr");
|
||||
|
|
|
@ -68,6 +68,7 @@ private:
|
|||
string m_namedScope; // Name of begin blocks above us
|
||||
string m_unnamedScope; // Name of begin blocks, including unnamed blocks
|
||||
int m_ifDepth = 0; // Current if depth
|
||||
bool m_underFork = false; // True if the current statement is directly under a fork
|
||||
|
||||
// METHODS
|
||||
VL_DEBUG_FUNC; // Declare debug()
|
||||
|
@ -120,6 +121,12 @@ private:
|
|||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstFork* nodep) override {
|
||||
VL_RESTORER(m_underFork);
|
||||
m_underFork = true;
|
||||
dotNames(nodep, "__FORK__");
|
||||
nodep->name("");
|
||||
}
|
||||
virtual void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_modp);
|
||||
{
|
||||
|
@ -169,11 +176,19 @@ private:
|
|||
VL_RESTORER(m_namedScope);
|
||||
VL_RESTORER(m_unnamedScope);
|
||||
{
|
||||
dotNames(nodep, "__BEGIN__");
|
||||
|
||||
{
|
||||
VL_RESTORER(m_underFork);
|
||||
m_underFork = false;
|
||||
dotNames(nodep, "__BEGIN__");
|
||||
}
|
||||
UASSERT_OBJ(!nodep->genforp(), nodep, "GENFORs should have been expanded earlier");
|
||||
|
||||
// Cleanup
|
||||
if (m_underFork) {
|
||||
// If we're under a fork, keep this begin to group its statements together
|
||||
nodep->name("");
|
||||
return;
|
||||
}
|
||||
AstNode* addsp = nullptr;
|
||||
if (AstNode* const stmtsp = nodep->stmtsp()) {
|
||||
stmtsp->unlinkFrBackWithNext();
|
||||
|
@ -251,6 +266,8 @@ private:
|
|||
}
|
||||
// VISITORS - LINT CHECK
|
||||
virtual void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered
|
||||
VL_RESTORER(m_underFork);
|
||||
m_underFork = false;
|
||||
// Check IFDEPTH warning - could be in other transform files if desire
|
||||
VL_RESTORER(m_ifDepth);
|
||||
if (m_ifDepth == -1 || v3Global.opt.ifDepth() < 1) { // Turned off
|
||||
|
@ -264,7 +281,11 @@ private:
|
|||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
virtual void visit(AstNode* nodep) override {
|
||||
VL_RESTORER(m_underFork);
|
||||
m_underFork = false;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
|
|
|
@ -159,7 +159,8 @@ private:
|
|||
const AstNode* const backp = nodep->backp();
|
||||
if (nodep->access().isReadOnly() && !VN_IS(backp, CCast) && VN_IS(backp, NodeMath)
|
||||
&& !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor)
|
||||
&& (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec())
|
||||
&& (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec()
|
||||
&& !nodep->varp()->basicp()->isForkSync())
|
||||
&& backp->width() && castSize(nodep) != castSize(nodep->varp())) {
|
||||
// Cast vars to IData first, else below has upper bits wrongly set
|
||||
// CData x=3; out = (QData)(x<<30);
|
||||
|
|
|
@ -2853,6 +2853,7 @@ private:
|
|||
// Zero elimination
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
if (nodep->timingControlp()) m_hasJumpDelay = true;
|
||||
if (m_doNConst && replaceNodeAssign(nodep)) return;
|
||||
}
|
||||
virtual void visit(AstAssignAlias* nodep) override {
|
||||
|
@ -3164,10 +3165,6 @@ private:
|
|||
//-----
|
||||
// Jump elimination
|
||||
|
||||
virtual void visit(AstDelay* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
m_hasJumpDelay = true;
|
||||
}
|
||||
virtual void visit(AstJumpGo* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
// Jump to label where label immediately follows label is not useful
|
||||
|
@ -3563,6 +3560,7 @@ private:
|
|||
<< nodep->prettyTypeName() << " to constant.");
|
||||
}
|
||||
} else {
|
||||
if (nodep->isTimingControl()) m_hasJumpDelay = true;
|
||||
// Calculate the width of this operation
|
||||
if (m_params && !nodep->width()) nodep = V3Width::widthParamsEdit(nodep);
|
||||
iterateChildren(nodep);
|
||||
|
|
|
@ -280,6 +280,7 @@ private:
|
|||
} else { // Track like any other statement
|
||||
iterateAndNextNull(nodep->lhsp());
|
||||
}
|
||||
iterateNull(nodep->timingControlp());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,8 @@ private:
|
|||
// AstVarScope::user1p() -> AstVarScope*. Points to temp var created.
|
||||
// AstVarScope::user2p() -> AstActive*. Points to activity block of signal
|
||||
// (valid when AstVarScope::user1p is valid)
|
||||
// AstVarScope::user3p() -> AstAlwaysPost*. Post block for this variable used for
|
||||
// AssignDlys in suspendable processes
|
||||
// AstVarScope::user4p() -> AstAlwaysPost*. Post block for this variable
|
||||
// AstVarScope::user5p() -> AstVarRef*. Last blocking or non-blocking reference
|
||||
// AstVar::user2() -> bool. Set true if already made warning
|
||||
|
@ -92,6 +94,8 @@ private:
|
|||
AstActive* m_activep = nullptr; // Current activate
|
||||
const AstCFunc* m_cfuncp = nullptr; // Current public C Function
|
||||
AstAssignDly* m_nextDlyp = nullptr; // Next delayed assignment in a list of assignments
|
||||
AstNodeProcedure* m_procp = nullptr; // Current process
|
||||
std::set<AstSenTree*> m_timingDomains; // Timing resume domains
|
||||
bool m_inDly = false; // True in delayed assignments
|
||||
bool m_inLoop = false; // True in for loops
|
||||
bool m_inInitial = false; // True in static initializers and initial blocks
|
||||
|
@ -203,7 +207,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
AstNode* createDlyArray(AstAssignDly* nodep, AstNode* lhsp) {
|
||||
AstNode* createDlyOnSet(AstAssignDly* nodep, AstNode* lhsp) {
|
||||
// Create delayed assignment
|
||||
// See top of this file for transformation
|
||||
// Return the new LHS for the assignment, Null = unlink
|
||||
|
@ -211,17 +215,24 @@ private:
|
|||
AstNode* newlhsp = nullptr; // nullptr = unlink old assign
|
||||
const AstSel* bitselp = nullptr;
|
||||
AstArraySel* arrayselp = nullptr;
|
||||
AstVarRef* varrefp = nullptr;
|
||||
if (VN_IS(lhsp, Sel)) {
|
||||
bitselp = VN_AS(lhsp, Sel);
|
||||
arrayselp = VN_AS(bitselp->fromp(), ArraySel);
|
||||
} else {
|
||||
arrayselp = VN_CAST(bitselp->fromp(), ArraySel);
|
||||
if (!arrayselp) varrefp = VN_AS(bitselp->fromp(), VarRef);
|
||||
} else if (VN_IS(lhsp, ArraySel)) {
|
||||
arrayselp = VN_AS(lhsp, ArraySel);
|
||||
} else {
|
||||
varrefp = VN_AS(lhsp, VarRef);
|
||||
}
|
||||
if (arrayselp) {
|
||||
UASSERT_OBJ(!VN_IS(arrayselp->dtypep()->skipRefp(), UnpackArrayDType), nodep,
|
||||
"ArraySel with unpacked arrays should have been removed in V3Slice");
|
||||
UINFO(4, "AssignDlyArray: " << nodep << endl);
|
||||
} else {
|
||||
UASSERT_OBJ(varrefp, nodep, "No arraysel nor varref");
|
||||
UINFO(4, "AssignDlyOnSet: " << nodep << endl);
|
||||
}
|
||||
UASSERT_OBJ(arrayselp, nodep, "No arraysel under bitsel?");
|
||||
UASSERT_OBJ(!VN_IS(arrayselp->dtypep()->skipRefp(), UnpackArrayDType), nodep,
|
||||
"ArraySel with unpacked arrays should have been removed in V3Slice");
|
||||
UINFO(4, "AssignDlyArray: " << nodep << endl);
|
||||
//
|
||||
//=== Dimensions: __Vdlyvdim__
|
||||
std::deque<AstNode*> dimvalp; // Assignment value for each dimension of assignment
|
||||
AstNode* dimselp = arrayselp;
|
||||
|
@ -229,7 +240,7 @@ private:
|
|||
AstNode* const valp = VN_AS(dimselp, ArraySel)->bitp()->unlinkFrBack();
|
||||
dimvalp.push_front(valp);
|
||||
}
|
||||
AstVarRef* const varrefp = VN_AS(dimselp, VarRef);
|
||||
if (dimselp) varrefp = VN_AS(dimselp, VarRef);
|
||||
UASSERT_OBJ(varrefp, nodep, "No var underneath arraysels");
|
||||
UASSERT_OBJ(varrefp->varScopep(), varrefp, "Var didn't get varscoped in V3Scope.cpp");
|
||||
varrefp->unlinkFrBack();
|
||||
|
@ -292,7 +303,7 @@ private:
|
|||
AstVarScope* setvscp;
|
||||
AstAssignPre* setinitp = nullptr;
|
||||
|
||||
if (nodep->user3p()) {
|
||||
if (!m_procp->isSuspendable() && nodep->user3p()) {
|
||||
// Simplistic optimization. If the previous statement in same scope was also a =>,
|
||||
// then we told this nodep->user3 we can use its Vdlyvset rather than making a new one.
|
||||
// This is good for code like:
|
||||
|
@ -303,9 +314,12 @@ private:
|
|||
const string setvarname
|
||||
= (string("__Vdlyvset__") + oldvarp->shortName() + "__v" + cvtToStr(modVecNum));
|
||||
setvscp = createVarSc(varrefp->varScopep(), setvarname, 1, nullptr);
|
||||
setinitp = new AstAssignPre(nodep->fileline(),
|
||||
new AstVarRef(nodep->fileline(), setvscp, VAccess::WRITE),
|
||||
new AstConst(nodep->fileline(), 0));
|
||||
if (!m_procp->isSuspendable()) {
|
||||
// Suspendables reset __Vdlyvset__ in the AstAlwaysPost
|
||||
setinitp = new AstAssignPre{
|
||||
nodep->fileline(), new AstVarRef{nodep->fileline(), setvscp, VAccess::WRITE},
|
||||
new AstConst{nodep->fileline(), 0}};
|
||||
}
|
||||
AstAssign* const setassignp = new AstAssign(
|
||||
nodep->fileline(), new AstVarRef(nodep->fileline(), setvscp, VAccess::WRITE),
|
||||
new AstConst(nodep->fileline(), AstConst::BitTrue()));
|
||||
|
@ -331,19 +345,36 @@ private:
|
|||
// Build "IF (changeit) ...
|
||||
UINFO(9, " For " << setvscp << endl);
|
||||
UINFO(9, " & " << varrefp << endl);
|
||||
AstAlwaysPost* finalp = VN_AS(varrefp->varScopep()->user4p(), AlwaysPost);
|
||||
if (finalp) {
|
||||
AstActive* const oldactivep = VN_AS(finalp->user2p(), Active);
|
||||
checkActivePost(varrefp, oldactivep);
|
||||
if (setinitp) oldactivep->addStmtsp(setinitp);
|
||||
} else { // first time we've dealt with this memory
|
||||
finalp = new AstAlwaysPost(nodep->fileline(), nullptr /*sens*/, nullptr /*body*/);
|
||||
UINFO(9, " Created " << finalp << endl);
|
||||
AstActive* const newactp = createActive(varrefp);
|
||||
newactp->addStmtsp(finalp);
|
||||
varrefp->varScopep()->user4p(finalp);
|
||||
finalp->user2p(newactp);
|
||||
if (setinitp) newactp->addStmtsp(setinitp);
|
||||
AstAlwaysPost* finalp = nullptr;
|
||||
if (m_procp->isSuspendable()) {
|
||||
finalp = VN_AS(varrefp->varScopep()->user3p(), AlwaysPost);
|
||||
if (!finalp) {
|
||||
FileLine* const flp = nodep->fileline();
|
||||
finalp = new AstAlwaysPost{flp, nullptr, nullptr};
|
||||
UINFO(9, " Created " << finalp << endl);
|
||||
if (!m_procp->user3p()) {
|
||||
AstActive* const newactp = createActive(varrefp);
|
||||
m_procp->user3p(newactp);
|
||||
varrefp->varScopep()->user3p(finalp);
|
||||
}
|
||||
AstActive* const actp = VN_AS(m_procp->user3p(), Active);
|
||||
actp->addStmtsp(finalp);
|
||||
}
|
||||
} else {
|
||||
finalp = VN_AS(varrefp->varScopep()->user4p(), AlwaysPost);
|
||||
if (finalp) {
|
||||
AstActive* const oldactivep = VN_AS(finalp->user2p(), Active);
|
||||
checkActivePost(varrefp, oldactivep);
|
||||
if (setinitp) oldactivep->addStmtsp(setinitp);
|
||||
} else { // first time we've dealt with this memory
|
||||
finalp = new AstAlwaysPost{nodep->fileline(), nullptr /*sens*/, nullptr /*body*/};
|
||||
UINFO(9, " Created " << finalp << endl);
|
||||
AstActive* const newactp = createActive(varrefp);
|
||||
newactp->addStmtsp(finalp);
|
||||
varrefp->varScopep()->user4p(finalp);
|
||||
finalp->user2p(newactp);
|
||||
if (setinitp) newactp->addStmtsp(setinitp);
|
||||
}
|
||||
}
|
||||
AstIf* postLogicp;
|
||||
if (finalp->user3p() == setvscp) {
|
||||
|
@ -361,6 +392,11 @@ private:
|
|||
finalp->user4p(postLogicp); // and the associated IF, as we may be able to reuse it
|
||||
}
|
||||
postLogicp->addIfsp(new AstAssign(nodep->fileline(), selectsp, valreadp));
|
||||
if (m_procp->isSuspendable()) {
|
||||
FileLine* const flp = nodep->fileline();
|
||||
postLogicp->addIfsp(new AstAssign{flp, new AstVarRef{flp, setvscp, VAccess::WRITE},
|
||||
new AstConst{flp, 0}});
|
||||
}
|
||||
return newlhsp;
|
||||
}
|
||||
|
||||
|
@ -393,10 +429,38 @@ private:
|
|||
iterateChildren(nodep);
|
||||
}
|
||||
}
|
||||
virtual void visit(AstNodeProcedure* nodep) override {
|
||||
m_procp = nodep;
|
||||
m_timingDomains.clear();
|
||||
iterateChildren(nodep);
|
||||
m_procp = nullptr;
|
||||
if (m_timingDomains.empty()) return;
|
||||
if (auto* const actp = VN_AS(nodep->user3p(), Active)) {
|
||||
// Merge all timing domains (and possibly the active's domain) to create a sentree for
|
||||
// the post logic
|
||||
// TODO: allow multiple sentrees per active, so we don't have to merge them and create
|
||||
// a new trigger
|
||||
auto* clockedDomain
|
||||
= actp->sensesp()->hasClocked() ? actp->sensesp()->cloneTree(false) : nullptr;
|
||||
for (auto* const domainp : m_timingDomains) {
|
||||
if (!clockedDomain) {
|
||||
clockedDomain = domainp->cloneTree(false);
|
||||
} else {
|
||||
clockedDomain->addSensesp(domainp->sensesp()->cloneTree(true));
|
||||
clockedDomain->multi(true); // Comment that it was made from multiple domains
|
||||
}
|
||||
}
|
||||
// We cannot constify the sentree using V3Const as user1-5 is already taken up by
|
||||
// V3Delayed
|
||||
actp->sensesp(clockedDomain);
|
||||
actp->sensesStorep(clockedDomain);
|
||||
}
|
||||
}
|
||||
virtual void visit(AstCAwait* nodep) override { m_timingDomains.insert(nodep->sensesp()); }
|
||||
virtual void visit(AstFireEvent* nodep) override {
|
||||
UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent");
|
||||
FileLine* const flp = nodep->fileline();
|
||||
if (nodep->isDeleyed()) {
|
||||
if (nodep->isDelayed()) {
|
||||
AstVarRef* const vrefp = VN_AS(nodep->operandp(), VarRef);
|
||||
vrefp->unlinkFrBack();
|
||||
const string newvarname = (string("__Vdly__") + vrefp->varp()->shortName());
|
||||
|
@ -442,12 +506,14 @@ private:
|
|||
nodep->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: Delayed assignment inside public function/task");
|
||||
}
|
||||
if (VN_IS(nodep->lhsp(), ArraySel)
|
||||
|| (VN_IS(nodep->lhsp(), Sel)
|
||||
&& VN_IS(VN_AS(nodep->lhsp(), Sel)->fromp(), ArraySel))) {
|
||||
AstNode* const lhsp = nodep->lhsp()->unlinkFrBack();
|
||||
AstNode* const newlhsp = createDlyArray(nodep, lhsp);
|
||||
if (m_inLoop) {
|
||||
UASSERT_OBJ(m_procp, nodep, "Delayed assignment not under process");
|
||||
const bool isArray = VN_IS(nodep->lhsp(), ArraySel)
|
||||
|| (VN_IS(nodep->lhsp(), Sel)
|
||||
&& VN_IS(VN_AS(nodep->lhsp(), Sel)->fromp(), ArraySel));
|
||||
if (m_procp->isSuspendable() || isArray) {
|
||||
AstNode* const lhsp = nodep->lhsp();
|
||||
AstNode* const newlhsp = createDlyOnSet(nodep, lhsp);
|
||||
if (m_inLoop && isArray) {
|
||||
nodep->v3warn(BLKLOOPINIT, "Unsupported: Delayed assignment to array inside for "
|
||||
"loops (non-delayed is ok - see docs)");
|
||||
}
|
||||
|
@ -456,11 +522,12 @@ private:
|
|||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays");
|
||||
}
|
||||
if (newlhsp) {
|
||||
if (nodep->lhsp()) nodep->lhsp()->unlinkFrBack();
|
||||
nodep->lhsp(newlhsp);
|
||||
} else {
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
}
|
||||
VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
|
||||
if (!lhsp->backp()) VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
|
||||
} else {
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
|
|
@ -684,6 +684,12 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
|
|||
} else if (basicp && basicp->keyword() == VBasicDTypeKwd::STRING) {
|
||||
// String's constructor deals with it
|
||||
return "";
|
||||
} else if (basicp && basicp->isForkSync()) {
|
||||
return "";
|
||||
} else if (basicp && basicp->isDelayScheduler()) {
|
||||
return "";
|
||||
} else if (basicp && basicp->isTriggerScheduler()) {
|
||||
return "";
|
||||
} else if (basicp) {
|
||||
const bool zeroit
|
||||
= (varp->attrFileDescr() // Zero so we don't do file IO if never $fopen
|
||||
|
|
|
@ -413,6 +413,11 @@ public:
|
|||
puts(funcp->nameProtect());
|
||||
emitCCallArgs(nodep, "");
|
||||
}
|
||||
virtual void visit(AstCAwait* nodep) override {
|
||||
puts("co_await ");
|
||||
iterate(nodep->exprp());
|
||||
if (nodep->isStatement()) puts(";\n");
|
||||
}
|
||||
virtual void visit(AstCNew* nodep) override {
|
||||
bool comma = false;
|
||||
puts("std::make_shared<" + prefixNameProtect(nodep->dtypep()) + ">(");
|
||||
|
|
|
@ -313,6 +313,7 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
|
||||
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
|
||||
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
|
||||
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
|
||||
|
||||
emitAll(modp);
|
||||
|
||||
|
|
|
@ -254,6 +254,10 @@ class EmitCImp final : EmitCFunc {
|
|||
puts("(");
|
||||
putsQuoted(varp->nameProtect());
|
||||
puts(")\n");
|
||||
} else if (dtypep->isDelayScheduler()) {
|
||||
puts(", ");
|
||||
puts(varp->nameProtect());
|
||||
puts("{*symsp->_vm_contextp__}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,12 @@ private:
|
|||
puts(/**/ "// Evaluate model\n");
|
||||
puts(/**/ "topp->eval();\n");
|
||||
puts(/**/ "// Advance time\n");
|
||||
puts(/**/ "contextp->timeInc(1);\n");
|
||||
if (v3Global.rootp()->delaySchedulerp()) {
|
||||
puts("if (!topp->eventsPending()) break;\n");
|
||||
puts("contextp->time(topp->nextTimeSlot());\n");
|
||||
} else {
|
||||
puts("contextp->timeInc(1);\n");
|
||||
}
|
||||
|
||||
puts("}\n");
|
||||
puts("\n");
|
||||
|
|
|
@ -113,6 +113,8 @@ class CMakeEmitter final {
|
|||
cmake_set_raw(*of, name + "_SC", v3Global.opt.systemC() ? "1" : "0");
|
||||
*of << "# Coverage output mode? 0/1 (from --coverage)\n";
|
||||
cmake_set_raw(*of, name + "_COVERAGE", v3Global.opt.coverage() ? "1" : "0");
|
||||
*of << "# Timing mode? 0/1\n";
|
||||
cmake_set_raw(*of, name + "_TIMING", v3Global.usesTiming() ? "1" : "0");
|
||||
*of << "# Threaded output mode? 0/1/N threads (from --threads)\n";
|
||||
cmake_set_raw(*of, name + "_THREADS", cvtToStr(v3Global.opt.threads()));
|
||||
*of << "# VCD Tracing output mode? 0/1 (from --trace)\n";
|
||||
|
@ -169,6 +171,9 @@ class CMakeEmitter final {
|
|||
+ ".cpp");
|
||||
}
|
||||
}
|
||||
if (v3Global.usesTiming()) {
|
||||
global.emplace_back("${VERILATOR_ROOT}/include/verilated_timing.cpp");
|
||||
}
|
||||
if (v3Global.opt.threads()) {
|
||||
global.emplace_back("${VERILATOR_ROOT}/include/verilated_threads.cpp");
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ class EmitCModel final : public EmitCFunc {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (optSystemC() && v3Global.usesTiming()) puts("sc_event trigger_eval;\n");
|
||||
|
||||
// Cells instantiated by the top level (for access to /* verilator public */)
|
||||
puts("\n// CELLS\n"
|
||||
|
@ -162,7 +163,12 @@ class EmitCModel final : public EmitCFunc {
|
|||
if (!optSystemC()) {
|
||||
puts("/// Evaluate the model. Application must call when inputs change.\n");
|
||||
}
|
||||
puts("void eval() { eval_step(); " + callEvalEndStep + "}\n");
|
||||
if (optSystemC() && v3Global.usesTiming()) {
|
||||
puts("void eval();\n");
|
||||
puts("void eval_sens();\n");
|
||||
} else {
|
||||
puts("void eval() { eval_step(); " + callEvalEndStep + "}\n");
|
||||
}
|
||||
if (!optSystemC()) {
|
||||
puts("/// Evaluate when calling multiple units/models per time step.\n");
|
||||
}
|
||||
|
@ -184,6 +190,11 @@ class EmitCModel final : public EmitCFunc {
|
|||
ofp()->putsPrivate(false); // public:
|
||||
puts("void final();\n");
|
||||
|
||||
puts("/// Are there scheduled events to handle?\n");
|
||||
puts("bool eventsPending();\n");
|
||||
puts("/// Returns time at next time slot. Aborts if !eventsPending()\n");
|
||||
puts("uint64_t nextTimeSlot();\n");
|
||||
|
||||
if (v3Global.opt.trace()) {
|
||||
puts("/// Trace signals in the model; called by application code\n");
|
||||
puts("void trace(" + v3Global.opt.traceClassBase()
|
||||
|
@ -278,6 +289,7 @@ class EmitCModel final : public EmitCFunc {
|
|||
// Create sensitivity list for when to evaluate the model.
|
||||
putsDecoration("// Sensitivities on all clocks and combinational inputs\n");
|
||||
puts("SC_METHOD(eval);\n");
|
||||
if (v3Global.usesTiming()) puts("SC_METHOD(eval_sens);\n");
|
||||
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
||||
if (const AstVar* const varp = VN_CAST(nodep, Var)) {
|
||||
if (varp->isNonOutput() && (varp->isScSensitive() || varp->isUsedClock())) {
|
||||
|
@ -343,6 +355,24 @@ class EmitCModel final : public EmitCFunc {
|
|||
puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n");
|
||||
puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n");
|
||||
|
||||
if (optSystemC() && v3Global.usesTiming()) {
|
||||
// ::eval
|
||||
puts("\nvoid " + topClassName() + "::eval() {\n");
|
||||
puts("eval_step();\n");
|
||||
puts("if (eventsPending()) {\n");
|
||||
puts("sc_time dt = sc_time::from_value(nextTimeSlot() - contextp()->time());\n");
|
||||
puts("next_trigger(dt, trigger_eval);\n");
|
||||
puts("} else {\n");
|
||||
puts("next_trigger(trigger_eval);\n");
|
||||
puts("}\n");
|
||||
puts("}\n");
|
||||
|
||||
// ::eval_sens
|
||||
puts("\nvoid " + topClassName() + "::eval_sens() {\n");
|
||||
puts("trigger_eval.notify();\n");
|
||||
puts("}\n");
|
||||
}
|
||||
|
||||
// ::eval_step
|
||||
puts("\nvoid " + topClassName() + "::eval_step() {\n");
|
||||
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName()
|
||||
|
@ -408,6 +438,24 @@ class EmitCModel final : public EmitCFunc {
|
|||
puts("}\n");
|
||||
}
|
||||
|
||||
if (v3Global.usesTiming()) {
|
||||
putSectionDelimiter("Events and timing");
|
||||
if (auto* const delaySchedp = v3Global.rootp()->delaySchedulerp()) {
|
||||
puts("bool " + topClassName() + "::eventsPending() { return !vlSymsp->TOP.");
|
||||
puts(delaySchedp->nameProtect());
|
||||
puts(".empty(); }\n\n");
|
||||
puts("uint64_t " + topClassName() + "::nextTimeSlot() { return vlSymsp->TOP.");
|
||||
puts(delaySchedp->nameProtect());
|
||||
puts(".nextTimeSlot(); }\n");
|
||||
} else {
|
||||
puts("bool " + topClassName() + "::eventsPending() { return false; }\n\n");
|
||||
puts("uint64_t " + topClassName() + "::nextTimeSlot() {\n");
|
||||
puts("VL_FATAL_MT(__FILE__, __LINE__, \"\", \"%Error: No delays in the "
|
||||
"design\");\n");
|
||||
puts("return 0;\n}\n");
|
||||
}
|
||||
}
|
||||
|
||||
putSectionDelimiter("Utilities");
|
||||
|
||||
if (!optSystemC()) {
|
||||
|
|
|
@ -50,6 +50,10 @@ public:
|
|||
of.puts("\n### Switches...\n");
|
||||
of.puts("# C11 constructs required? 0/1 (always on now)\n");
|
||||
of.puts("VM_C11 = 1\n");
|
||||
of.puts("# Timing enabled? 0/1\n");
|
||||
of.puts("VM_TIMING = ");
|
||||
of.puts(v3Global.usesTiming() ? "1" : "0");
|
||||
of.puts("\n");
|
||||
of.puts("# Coverage output mode? 0/1 (from --coverage)\n");
|
||||
of.puts("VM_COVERAGE = ");
|
||||
of.puts(v3Global.opt.coverage() ? "1" : "0");
|
||||
|
@ -108,6 +112,7 @@ public:
|
|||
putMakeClassEntry(of, v3Global.opt.traceSourceLang() + ".cpp");
|
||||
}
|
||||
}
|
||||
if (v3Global.usesTiming()) putMakeClassEntry(of, "verilated_timing.cpp");
|
||||
if (v3Global.opt.threads()) putMakeClassEntry(of, "verilated_threads.cpp");
|
||||
if (v3Global.opt.usesProfiler()) {
|
||||
putMakeClassEntry(of, "verilated_profiler.cpp");
|
||||
|
|
|
@ -51,12 +51,15 @@ public:
|
|||
I_TRACING, // Tracing is on/off from /*verilator tracing_on/off*/
|
||||
I_LINT, // All lint messages
|
||||
I_DEF_NETTYPE_WIRE, // `default_nettype is WIRE (false=NONE)
|
||||
I_TIMING, // Enable timing from /*verilator timing_on/off*/
|
||||
// Error codes:
|
||||
E_DETECTARRAY, // Error: Unsupported: Can't detect changes on arrayed variable
|
||||
E_ENCAPSULATED, // Error: local/protected violation
|
||||
E_PORTSHORT, // Error: Output port is connected to a constant, electrical short
|
||||
E_UNSUPPORTED, // Error: Unsupported (generally)
|
||||
E_TASKNSVAR, // Error: Task I/O not simple
|
||||
E_NEEDTIMINGOPT, // Error: --timing/--no-timing option not specified
|
||||
E_NOTIMING, // Timing control encountered with --no-timing
|
||||
//
|
||||
// Warning codes:
|
||||
EC_FIRST_WARN, // Just a code so the program knows where to start warnings
|
||||
|
@ -83,6 +86,8 @@ public:
|
|||
DEFPARAM, // Style: Defparam
|
||||
DECLFILENAME, // Declaration doesn't match filename
|
||||
DEPRECATED, // Feature will be deprecated
|
||||
RISEFALLDLY, // Unsupported: rise/fall/turn-off delays
|
||||
MINTYPMAXDLY, // Unsupported: min/typ/max delay expressions
|
||||
ENDLABEL, // End lable name mismatch
|
||||
EOFNEWLINE, // End-of-file missing newline
|
||||
GENCLK, // Generated Clock. Historical, never issued.
|
||||
|
@ -135,8 +140,10 @@ public:
|
|||
USERINFO, // Elaboration time $info
|
||||
USERWARN, // Elaboration time $warning
|
||||
VARHIDDEN, // Hiding variable
|
||||
WAITCONST, // Wait condition is constant
|
||||
WIDTH, // Width mismatch
|
||||
WIDTHCONCAT, // Unsized numbers/parameters in concatenations
|
||||
ZERODLY, // #0 delay
|
||||
_ENUM_MAX
|
||||
// ***Add new elements below also***
|
||||
};
|
||||
|
@ -157,16 +164,16 @@ public:
|
|||
// Leading spaces indicate it can't be disabled.
|
||||
" MIN", " INFO", " FATAL", " FATALEXIT", " FATALSRC", " ERROR", " FIRST_NAMED",
|
||||
// Boolean
|
||||
" I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_DEF_NETTYPE_WIRE",
|
||||
" I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_DEF_NETTYPE_WIRE", " I_TIMING",
|
||||
// Errors
|
||||
"DETECTARRAY", "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR",
|
||||
"DETECTARRAY", "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR", "NEEDTIMINGOPT", "NOTIMING",
|
||||
// Warnings
|
||||
" EC_FIRST_WARN",
|
||||
"ALWCOMBORDER", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA",
|
||||
"BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE",
|
||||
"CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA",
|
||||
"CMPCONST", "COLONPLUS", "COMBDLY", "CONTASSREG",
|
||||
"DEFPARAM", "DECLFILENAME", "DEPRECATED",
|
||||
"DEFPARAM", "DECLFILENAME", "DEPRECATED", "RISEFALLDLY", "MINTYPMAXDLY",
|
||||
"ENDLABEL", "EOFNEWLINE", "GENCLK", "HIERBLOCK",
|
||||
"IFDEPTH", "IGNOREDRETURN",
|
||||
"IMPERFECTSCH", "IMPLICIT", "IMPORTSTAR", "IMPURE",
|
||||
|
@ -180,7 +187,7 @@ public:
|
|||
"UNDRIVEN", "UNOPT", "UNOPTFLAT", "UNOPTTHREADS",
|
||||
"UNPACKED", "UNSIGNED", "UNUSED",
|
||||
"USERERROR", "USERFATAL", "USERINFO", "USERWARN",
|
||||
"VARHIDDEN", "WIDTH", "WIDTHCONCAT",
|
||||
"VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "ZERODLY",
|
||||
" MAX"
|
||||
};
|
||||
// clang-format on
|
||||
|
@ -197,7 +204,8 @@ public:
|
|||
bool pretendError() const {
|
||||
return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT
|
||||
|| m_e == CONTASSREG || m_e == IMPURE || m_e == PINNOTFOUND || m_e == PKGNODECL
|
||||
|| m_e == PROCASSWIRE); // Says IEEE
|
||||
|| m_e == PROCASSWIRE // Says IEEE
|
||||
|| m_e == ZERODLY);
|
||||
}
|
||||
// Warnings to mention manual
|
||||
bool mentionManual() const {
|
||||
|
|
|
@ -222,6 +222,8 @@ public:
|
|||
void coverageOn(bool flag) { warnOn(V3ErrorCode::I_COVERAGE, flag); }
|
||||
bool tracingOn() const { return m_warnOn.test(V3ErrorCode::I_TRACING); }
|
||||
void tracingOn(bool flag) { warnOn(V3ErrorCode::I_TRACING, flag); }
|
||||
bool timingOn() const { return m_warnOn.test(V3ErrorCode::I_TIMING); }
|
||||
void timingOn(bool flag) { warnOn(V3ErrorCode::I_TIMING, flag); }
|
||||
|
||||
// METHODS - Global
|
||||
// <command-line> and <built-in> match what GCC outputs
|
||||
|
|
|
@ -247,6 +247,8 @@ private:
|
|||
m_substTreep = nodep->rhsp();
|
||||
if (!VN_IS(nodep->lhsp(), NodeVarRef)) {
|
||||
clearSimple("ASSIGN(non-VARREF)");
|
||||
} else if (nodep->isTimingControl()) {
|
||||
clearSimple("Timing control");
|
||||
} else {
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
@ -343,6 +345,13 @@ private:
|
|||
VDouble0 m_statAssignMerged; // Statistic tracking
|
||||
|
||||
// METHODS
|
||||
void checkTimingControl(AstNode* nodep) {
|
||||
if (nodep->isTimingControl() && m_logicVertexp) {
|
||||
m_logicVertexp->clearReducibleAndDedupable("TimingControl");
|
||||
m_logicVertexp->setConsumed("TimingControl");
|
||||
}
|
||||
}
|
||||
|
||||
void iterateNewStmt(AstNode* nodep, const char* nonReducibleReason,
|
||||
const char* consumeReason) {
|
||||
if (m_scopep) {
|
||||
|
@ -357,6 +366,7 @@ private:
|
|||
}
|
||||
if (consumeReason) m_logicVertexp->setConsumed(consumeReason);
|
||||
if (VN_IS(nodep, SenItem)) m_logicVertexp->setConsumed("senItem");
|
||||
checkTimingControl(nodep);
|
||||
iterateChildren(nodep);
|
||||
m_logicVertexp = nullptr;
|
||||
}
|
||||
|
@ -555,6 +565,7 @@ private:
|
|||
virtual void visit(AstNode* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
if (nodep->isOutputter() && m_logicVertexp) m_logicVertexp->setConsumed("outputter");
|
||||
checkTimingControl(nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
|
@ -106,6 +106,7 @@ class V3Global final {
|
|||
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
|
||||
bool m_usesTiming = false; // Design uses timing constructs
|
||||
bool m_hasForceableSignals = false; // Need to apply V3Force pass
|
||||
bool m_hasSCTextSections = false; // Has `systemc_* sections that need to be emitted
|
||||
bool m_useParallelBuild = false; // Use parallel build for model
|
||||
|
@ -148,6 +149,8 @@ public:
|
|||
void dpi(bool flag) { m_dpi = flag; }
|
||||
bool hasEvents() const { return m_hasEvents; }
|
||||
void setHasEvents() { m_hasEvents = true; }
|
||||
bool usesTiming() const { return m_usesTiming; }
|
||||
void setUsesTiming() { m_usesTiming = true; }
|
||||
bool hasForceableSignals() const { return m_hasForceableSignals; }
|
||||
void setHasForceableSignals() { m_hasForceableSignals = true; }
|
||||
bool hasSCTextSections() const { return m_hasSCTextSections; }
|
||||
|
|
|
@ -262,6 +262,11 @@ private:
|
|||
m_hash += nodep->name();
|
||||
});
|
||||
}
|
||||
virtual void visit(AstCAwait* nodep) override {
|
||||
m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, HASH_CHILDREN, [=]() { //
|
||||
iterateNull(nodep->sensesp());
|
||||
});
|
||||
}
|
||||
virtual void visit(AstCoverInc* nodep) override {
|
||||
m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() { //
|
||||
iterateNull(nodep->declp());
|
||||
|
|
|
@ -42,6 +42,7 @@ private:
|
|||
const AstNode* const m_startNodep; // Start node of count
|
||||
bool m_tracingCall = false; // Iterating into a CCall to a CFunc
|
||||
bool m_inCFunc = false; // Inside AstCFunc
|
||||
bool m_ignoreRemaining = false; // Ignore remaining statements in the block
|
||||
const bool m_assertNoDups; // Check for duplicates
|
||||
const std::ostream* const m_osp; // Dump file
|
||||
|
||||
|
@ -81,7 +82,12 @@ public:
|
|||
uint32_t instrCount() const { return m_instrCount; }
|
||||
|
||||
private:
|
||||
void reset() {
|
||||
m_instrCount = 0;
|
||||
m_ignoreRemaining = false;
|
||||
}
|
||||
uint32_t startVisitBase(AstNode* nodep) {
|
||||
UASSERT_OBJ(!m_ignoreRemaining, nodep, "Should not reach here if ignoring");
|
||||
if (m_assertNoDups && !m_inCFunc) {
|
||||
// Ensure we don't count the same node twice
|
||||
//
|
||||
|
@ -110,7 +116,7 @@ private:
|
|||
void endVisitBase(uint32_t savedCount, AstNode* nodep) {
|
||||
UINFO(8, "cost " << std::setw(6) << std::left << m_instrCount << " " << nodep << endl);
|
||||
markCost(nodep);
|
||||
m_instrCount += savedCount;
|
||||
if (!m_ignoreRemaining) m_instrCount += savedCount;
|
||||
}
|
||||
void markCost(AstNode* nodep) {
|
||||
if (m_osp) nodep->user4(m_instrCount + 1); // Else don't mark to avoid writeback
|
||||
|
@ -118,6 +124,7 @@ private:
|
|||
|
||||
// VISITORS
|
||||
virtual void visit(AstNodeSel* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
// This covers both AstArraySel and AstWordSel
|
||||
//
|
||||
// If some vector is a bazillion dwords long, and we're selecting 1
|
||||
|
@ -129,6 +136,7 @@ private:
|
|||
iterateAndNextNull(nodep->bitp());
|
||||
}
|
||||
virtual void visit(AstSel* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
// Similar to AstNodeSel above, a small select into a large vector
|
||||
// is not expensive. Count the cost of the AstSel itself (scales with
|
||||
// its width) and the cost of the lsbp() and widthp() nodes, but not
|
||||
|
@ -144,6 +152,7 @@ private:
|
|||
nodep->v3fatalSrc("AstMemberSel unhandled");
|
||||
}
|
||||
virtual void visit(AstConcat* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
// Nop.
|
||||
//
|
||||
// Ignore concat. The problem with counting concat is that when we
|
||||
|
@ -164,22 +173,24 @@ private:
|
|||
markCost(nodep);
|
||||
}
|
||||
virtual void visit(AstNodeIf* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateAndNextNull(nodep->condp());
|
||||
const uint32_t savedCount = m_instrCount;
|
||||
|
||||
UINFO(8, "ifsp:\n");
|
||||
m_instrCount = 0;
|
||||
reset();
|
||||
iterateAndNextNull(nodep->ifsp());
|
||||
uint32_t ifCount = m_instrCount;
|
||||
if (nodep->branchPred().unlikely()) ifCount = 0;
|
||||
|
||||
UINFO(8, "elsesp:\n");
|
||||
m_instrCount = 0;
|
||||
reset();
|
||||
iterateAndNextNull(nodep->elsesp());
|
||||
uint32_t elseCount = m_instrCount;
|
||||
if (nodep->branchPred().likely()) elseCount = 0;
|
||||
|
||||
reset();
|
||||
if (ifCount >= elseCount) {
|
||||
m_instrCount = savedCount + ifCount;
|
||||
if (nodep->elsesp()) nodep->elsesp()->user4(0); // Don't dump it
|
||||
|
@ -189,6 +200,7 @@ private:
|
|||
}
|
||||
}
|
||||
virtual void visit(AstNodeCond* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
// Just like if/else above, the ternary operator only evaluates
|
||||
// one of the two expressions, so only count the max.
|
||||
const VisitBase vb{this, nodep};
|
||||
|
@ -196,15 +208,16 @@ private:
|
|||
const uint32_t savedCount = m_instrCount;
|
||||
|
||||
UINFO(8, "?\n");
|
||||
m_instrCount = 0;
|
||||
reset();
|
||||
iterateAndNextNull(nodep->expr1p());
|
||||
const uint32_t ifCount = m_instrCount;
|
||||
|
||||
UINFO(8, ":\n");
|
||||
m_instrCount = 0;
|
||||
reset();
|
||||
iterateAndNextNull(nodep->expr2p());
|
||||
const uint32_t elseCount = m_instrCount;
|
||||
|
||||
reset();
|
||||
if (ifCount < elseCount) {
|
||||
m_instrCount = savedCount + elseCount;
|
||||
if (nodep->expr1p()) nodep->expr1p()->user4(0); // Don't dump it
|
||||
|
@ -213,6 +226,25 @@ private:
|
|||
if (nodep->expr2p()) nodep->expr2p()->user4(0); // Don't dump it
|
||||
}
|
||||
}
|
||||
virtual void visit(AstCAwait* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
iterateChildren(nodep);
|
||||
// Anything past a co_await is irrelevant
|
||||
m_ignoreRemaining = true;
|
||||
}
|
||||
virtual void visit(AstFork* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
uint32_t totalCount = m_instrCount;
|
||||
// Sum counts in each statement until the first await
|
||||
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
reset();
|
||||
iterate(stmtp);
|
||||
totalCount += m_instrCount;
|
||||
}
|
||||
m_instrCount = totalCount;
|
||||
m_ignoreRemaining = false;
|
||||
}
|
||||
virtual void visit(AstActive* nodep) override {
|
||||
// You'd think that the OrderLogicVertex's would be disjoint trees
|
||||
// of stuff in the AST, but it isn't so: V3Order makes an
|
||||
|
@ -230,6 +262,7 @@ private:
|
|||
UASSERT_OBJ(nodep == m_startNodep, nodep, "Multiple actives, or not start node");
|
||||
}
|
||||
virtual void visit(AstNodeCCall* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateChildren(nodep);
|
||||
m_tracingCall = true;
|
||||
|
@ -241,6 +274,7 @@ private:
|
|||
// from the root
|
||||
UASSERT_OBJ(m_tracingCall || nodep == m_startNodep, nodep,
|
||||
"AstCFunc not under AstCCall, or not start node");
|
||||
UASSERT_OBJ(!m_ignoreRemaining, nodep, "Should not be ignoring at the start of a CFunc");
|
||||
m_tracingCall = false;
|
||||
VL_RESTORER(m_inCFunc);
|
||||
{
|
||||
|
@ -248,8 +282,10 @@ private:
|
|||
const VisitBase vb{this, nodep};
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
m_ignoreRemaining = false;
|
||||
}
|
||||
virtual void visit(AstNode* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
|
|
@ -306,6 +306,12 @@ private:
|
|||
}
|
||||
}
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
if (nodep->isTimingControl()) {
|
||||
// V3Life doesn't understand time sense - don't optimize
|
||||
setNoopt();
|
||||
iterateChildren(nodep);
|
||||
return;
|
||||
}
|
||||
// Collect any used variables first, as lhs may also be on rhs
|
||||
// Similar code in V3Dead
|
||||
const uint64_t lastEdit = AstNode::editCountGbl(); // When it was last edited
|
||||
|
@ -326,7 +332,12 @@ private:
|
|||
}
|
||||
}
|
||||
virtual void visit(AstAssignDly* nodep) override {
|
||||
// Don't treat as normal assign; V3Life doesn't understand time sense
|
||||
// V3Life doesn't understand time sense
|
||||
if (nodep->isTimingControl()) {
|
||||
// Don't optimize
|
||||
setNoopt();
|
||||
}
|
||||
// Don't treat as normal assign
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
|
@ -433,7 +444,13 @@ private:
|
|||
}
|
||||
|
||||
virtual void visit(AstVar*) override {} // Don't want varrefs under it
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
virtual void visit(AstNode* nodep) override {
|
||||
if (nodep->isTimingControl()) {
|
||||
// V3Life doesn't understand time sense - don't optimize
|
||||
setNoopt();
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
|
|
|
@ -149,6 +149,25 @@ private:
|
|||
nodep->v3fatalSrc(
|
||||
"For statements should have been converted to while statements in V3Begin.cpp");
|
||||
}
|
||||
virtual void visit(AstDelay* nodep) override {
|
||||
m_insStmtp = nodep;
|
||||
iterateAndNextNull(nodep->lhsp());
|
||||
m_insStmtp = nullptr;
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
m_insStmtp = nullptr;
|
||||
}
|
||||
virtual void visit(AstEventControl* nodep) override {
|
||||
m_insStmtp = nullptr;
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
m_insStmtp = nullptr;
|
||||
}
|
||||
virtual void visit(AstWait* nodep) override {
|
||||
m_insStmtp = nodep;
|
||||
iterateAndNextNull(nodep->condp());
|
||||
m_insStmtp = nullptr;
|
||||
iterateAndNextNull(nodep->bodysp());
|
||||
m_insStmtp = nullptr;
|
||||
}
|
||||
virtual void visit(AstNodeStmt* nodep) override {
|
||||
if (!nodep->isStatement()) {
|
||||
iterateChildren(nodep);
|
||||
|
|
|
@ -175,17 +175,6 @@ private:
|
|||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstWait* nodep) override {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: wait statements");
|
||||
// Statements we'll just execute immediately; equivalent to if they followed this
|
||||
if (AstNode* const bodysp = nodep->bodysp()) {
|
||||
bodysp->unlinkFrBackWithNext();
|
||||
nodep->replaceWith(bodysp);
|
||||
} else {
|
||||
nodep->unlinkFrBack();
|
||||
}
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstWhile* nodep) override {
|
||||
// Don't need to track AstRepeat/AstFor as they have already been converted
|
||||
VL_RESTORER(m_loopp);
|
||||
|
|
|
@ -593,9 +593,7 @@ private:
|
|||
<< nodep->warnMore() << "... Suggest use a normal 'always'");
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (alwaysp && !alwaysp->sensesp()) {
|
||||
// Verilator is still ony supporting SenTrees under an always,
|
||||
// so allow the parser to handle everything and shim to
|
||||
// historical AST here
|
||||
// If the event control is at the top, move the sentree to the always
|
||||
if (AstSenTree* const sensesp = nodep->sensesp()) {
|
||||
sensesp->unlinkFrBackWithNext();
|
||||
alwaysp->sensesp(sensesp);
|
||||
|
|
|
@ -715,6 +715,14 @@ bool V3Options::systemCFound() {
|
|||
|| (!getenvSYSTEMC_INCLUDE().empty() && !getenvSYSTEMC_LIBDIR().empty()));
|
||||
}
|
||||
|
||||
bool V3Options::coroutineSupport() {
|
||||
#ifdef HAVE_COROUTINES
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// V3 Options notification methods
|
||||
|
||||
|
@ -1370,6 +1378,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
|
|||
m_timeOverridePrec = prec;
|
||||
}
|
||||
});
|
||||
DECL_OPTION("-timing", OnOff, &m_timing);
|
||||
DECL_OPTION("-top-module", Set, &m_topModule);
|
||||
DECL_OPTION("-top", Set, &m_topModule);
|
||||
DECL_OPTION("-trace", OnOff, &m_trace);
|
||||
|
@ -1748,6 +1757,7 @@ void V3Options::showVersion(bool verbose) {
|
|||
cout << endl;
|
||||
cout << "Features (based on environment or compiled-in support):\n";
|
||||
cout << " SystemC found = " << cvtToStr(systemCFound()) << endl;
|
||||
cout << " Coroutine support = " << cvtToStr(coroutineSupport()) << endl;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
|
|
@ -271,6 +271,7 @@ private:
|
|||
bool m_threadsCoarsen = true; // main switch: --threads-coarsen
|
||||
bool m_threadsDpiPure = true; // main switch: --threads-dpi all/pure
|
||||
bool m_threadsDpiUnpure = false; // main switch: --threads-dpi all
|
||||
VOptionBool m_timing; // main switch: --timing
|
||||
bool m_trace = false; // main switch: --trace
|
||||
bool m_traceCoverage = false; // main switch: --trace-coverage
|
||||
bool m_traceParams = true; // main switch: --trace-params
|
||||
|
@ -456,6 +457,7 @@ public:
|
|||
bool threadsDpiPure() const { return m_threadsDpiPure; }
|
||||
bool threadsDpiUnpure() const { return m_threadsDpiUnpure; }
|
||||
bool threadsCoarsen() const { return m_threadsCoarsen; }
|
||||
VOptionBool timing() const { return m_timing; }
|
||||
bool trace() const { return m_trace; }
|
||||
bool traceCoverage() const { return m_traceCoverage; }
|
||||
bool traceParams() const { return m_traceParams; }
|
||||
|
@ -647,6 +649,7 @@ public:
|
|||
static string getenvVERILATOR_ROOT();
|
||||
static bool systemCSystemWide();
|
||||
static bool systemCFound(); // SystemC installed, or environment points to it
|
||||
static bool coroutineSupport(); // Compiler supports coroutines
|
||||
|
||||
// METHODS (file utilities using these options)
|
||||
string fileExists(const string& filename);
|
||||
|
|
|
@ -1276,17 +1276,24 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp,
|
|||
|
||||
// Process procedures per statement (unless profCFuncs), so we can split CFuncs within
|
||||
// procedures. Everything else is handled in one go
|
||||
bool suspendable = false;
|
||||
bool slow = m_slow;
|
||||
if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) {
|
||||
suspendable = procp->isSuspendable();
|
||||
if (suspendable) slow = slow && !VN_IS(procp, Always);
|
||||
nodep = procp->bodysp();
|
||||
pushDeletep(procp);
|
||||
}
|
||||
|
||||
// Put suspendable processes into individual functions on their own
|
||||
if (suspendable) newFuncpr = nullptr;
|
||||
|
||||
// When profCFuncs, create a new function for all logic block
|
||||
if (v3Global.opt.profCFuncs()) newFuncpr = nullptr;
|
||||
|
||||
while (nodep) {
|
||||
// Split the CFunc if too large (but not when profCFuncs)
|
||||
if (!v3Global.opt.profCFuncs()
|
||||
if (!suspendable && !v3Global.opt.profCFuncs()
|
||||
&& (v3Global.opt.outputSplitCFuncs()
|
||||
&& v3Global.opt.outputSplitCFuncs() < newStmtsr)) {
|
||||
// Put every statement into a unique function to ease profiling or reduce function
|
||||
|
@ -1295,10 +1302,11 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp,
|
|||
}
|
||||
if (!newFuncpr && domainp != m_deleteDomainp) {
|
||||
const string name = cfuncName(modp, domainp, scopep, nodep);
|
||||
newFuncpr = new AstCFunc(nodep->fileline(), name, scopep);
|
||||
newFuncpr
|
||||
= new AstCFunc{nodep->fileline(), name, scopep, suspendable ? "VlCoroutine" : ""};
|
||||
newFuncpr->isStatic(false);
|
||||
newFuncpr->isLoose(true);
|
||||
newFuncpr->slow(m_slow);
|
||||
newFuncpr->slow(slow);
|
||||
newStmtsr = 0;
|
||||
scopep->addActivep(newFuncpr);
|
||||
// Create top call to it
|
||||
|
@ -1328,6 +1336,8 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp,
|
|||
|
||||
nodep = nextp;
|
||||
}
|
||||
// Put suspendable processes into individual functions on their own
|
||||
if (suspendable) newFuncpr = nullptr;
|
||||
|
||||
return activep;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
#include "V3Stats.h"
|
||||
#include "V3UniqueNames.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace V3Sched {
|
||||
|
@ -202,30 +201,45 @@ LogicClasses gatherLogicClasses(AstNetlist* netlistp) {
|
|||
// Simple ordering in source order
|
||||
|
||||
void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) {
|
||||
// Create new subfunc for scope
|
||||
const auto createNewSubFuncp = [&](AstScope* const scopep) {
|
||||
const string subName{funcp->name() + "__" + scopep->nameDotless()};
|
||||
AstCFunc* const subFuncp = new AstCFunc{scopep->fileline(), subName, scopep};
|
||||
subFuncp->isLoose(true);
|
||||
subFuncp->isConst(false);
|
||||
subFuncp->declPrivate(true);
|
||||
subFuncp->slow(funcp->slow());
|
||||
scopep->addActivep(subFuncp);
|
||||
// Call it from the top function
|
||||
funcp->addStmtsp(new AstCCall{scopep->fileline(), subFuncp});
|
||||
return subFuncp;
|
||||
};
|
||||
const VNUser1InUse user1InUse; // AstScope -> AstCFunc: the sub-function for the scope
|
||||
const VNUser2InUse user2InUse; // AstScope -> int: sub-function counter used for names
|
||||
for (const auto& pair : lbs) {
|
||||
AstScope* const scopep = pair.first;
|
||||
AstActive* const activep = pair.second;
|
||||
if (!scopep->user1p()) {
|
||||
// Create a sub-function per scope so we can V3Combine them later
|
||||
const string subName{funcp->name() + "__" + scopep->nameDotless()};
|
||||
AstCFunc* const subFuncp = new AstCFunc{scopep->fileline(), subName, scopep};
|
||||
subFuncp->isLoose(true);
|
||||
subFuncp->isConst(false);
|
||||
subFuncp->declPrivate(true);
|
||||
subFuncp->slow(funcp->slow());
|
||||
scopep->addActivep(subFuncp);
|
||||
scopep->user1p(subFuncp);
|
||||
// Call it from the top function
|
||||
funcp->addStmtsp(new AstCCall{scopep->fileline(), subFuncp});
|
||||
}
|
||||
AstCFunc* const subFuncp = VN_AS(scopep->user1p(), CFunc);
|
||||
// Create a sub-function per scope so we can V3Combine them later
|
||||
if (!scopep->user1p()) scopep->user1p(createNewSubFuncp(scopep));
|
||||
// Add statements to sub-function
|
||||
for (AstNode *logicp = activep->stmtsp(), *nextp; logicp; logicp = nextp) {
|
||||
auto* subFuncp = VN_AS(scopep->user1p(), CFunc);
|
||||
nextp = logicp->nextp();
|
||||
if (AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure)) {
|
||||
if (AstNode* const bodyp = procp->bodysp()) {
|
||||
if (AstNode* bodyp = procp->bodysp()) {
|
||||
bodyp->unlinkFrBackWithNext();
|
||||
// If the process is suspendable, we need a separate function (a coroutine)
|
||||
if (procp->isSuspendable()) {
|
||||
subFuncp = createNewSubFuncp(scopep);
|
||||
subFuncp->name(subFuncp->name() + "__" + cvtToStr(scopep->user2Inc()));
|
||||
subFuncp->rtnType("VlCoroutine");
|
||||
if (VN_IS(procp, Always)) {
|
||||
subFuncp->slow(false);
|
||||
FileLine* const flp = procp->fileline();
|
||||
bodyp
|
||||
= new AstWhile{flp, new AstConst{flp, AstConst::BitTrue{}}, bodyp};
|
||||
}
|
||||
}
|
||||
subFuncp->addStmtsp(bodyp);
|
||||
}
|
||||
} else {
|
||||
|
@ -432,6 +446,8 @@ class SenExprBuilder final {
|
|||
callp->dtypeSetBit();
|
||||
return {callp, false};
|
||||
}
|
||||
case VEdgeType::ET_TRUE: //
|
||||
return {currp(), false};
|
||||
default: // LCOV_EXCL_START
|
||||
senItemp->v3fatalSrc("Unknown edge type");
|
||||
return {nullptr, false};
|
||||
|
@ -937,7 +953,8 @@ void createEval(AstNetlist* netlistp, //
|
|||
AstVarScope* preTrigsp, //
|
||||
AstVarScope* nbaTrigsp, //
|
||||
AstCFunc* actFuncp, //
|
||||
AstCFunc* nbaFuncp //
|
||||
AstCFunc* nbaFuncp, //
|
||||
TimingKit& timingKit //
|
||||
) {
|
||||
FileLine* const flp = netlistp->fileline();
|
||||
|
||||
|
@ -967,7 +984,10 @@ void createEval(AstNetlist* netlistp, //
|
|||
= makeEvalLoop(
|
||||
netlistp, "act", "Active", actTrig.m_vscp, actTrig.m_dumpp,
|
||||
[&]() { // Trigger
|
||||
return new AstCCall{flp, actTrig.m_funcp};
|
||||
auto* const resultp = new AstCCall{flp, actTrig.m_funcp};
|
||||
// Commit trigger awaits from the previous iteration
|
||||
resultp->addNextNull(timingKit.createCommit(netlistp));
|
||||
return resultp;
|
||||
},
|
||||
[&]() { // Body
|
||||
AstNode* resultp = nullptr;
|
||||
|
@ -994,6 +1014,9 @@ void createEval(AstNetlist* netlistp, //
|
|||
resultp = AstNode::addNext(resultp, callp);
|
||||
}
|
||||
|
||||
// Resume triggered timing schedulers
|
||||
resultp = AstNode::addNextNull(resultp, timingKit.createResume(netlistp));
|
||||
|
||||
// Invoke body function
|
||||
return AstNode::addNext(resultp, new AstCCall{flp, actFuncp});
|
||||
})
|
||||
|
@ -1039,6 +1062,9 @@ void schedule(AstNetlist* netlistp) {
|
|||
V3Stats::addStat("Scheduling, " + name, size);
|
||||
};
|
||||
|
||||
// Step 0. Prepare timing-related logic and external domains
|
||||
auto timingKit = prepareTiming(netlistp);
|
||||
|
||||
// Step 1. Gather and classify all logic in the design
|
||||
LogicClasses logicClasses = gatherLogicClasses(netlistp);
|
||||
|
||||
|
@ -1113,7 +1139,8 @@ void schedule(AstNetlist* netlistp) {
|
|||
|
||||
const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, //
|
||||
&logicRegions.m_act, //
|
||||
&logicRegions.m_nba});
|
||||
&logicRegions.m_nba, //
|
||||
&timingKit.m_lbs});
|
||||
const TriggerKit& actTrig
|
||||
= createTriggers(netlistp, senExprBuilder, senTreeps, "act", extraTriggers);
|
||||
|
||||
|
@ -1163,6 +1190,8 @@ void schedule(AstNetlist* netlistp) {
|
|||
remapSensitivities(logicRegions.m_pre, preTrigMap);
|
||||
remapSensitivities(logicRegions.m_act, actTrigMap);
|
||||
remapSensitivities(logicReplicas.m_act, actTrigMap);
|
||||
remapSensitivities(timingKit.m_lbs, actTrigMap);
|
||||
const auto& actTimingDomains = timingKit.remapDomains(actTrigMap);
|
||||
|
||||
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
|
||||
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSenAct;
|
||||
|
@ -1175,7 +1204,9 @@ void schedule(AstNetlist* netlistp) {
|
|||
|
||||
AstCFunc* const actFuncp = V3Order::order(
|
||||
netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct,
|
||||
"act", false, false, [=](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
||||
"act", false, false, [&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
||||
auto it = actTimingDomains.find(vscp);
|
||||
if (it != actTimingDomains.end()) out = it->second;
|
||||
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct);
|
||||
});
|
||||
splitCheck(actFuncp);
|
||||
|
@ -1186,6 +1217,7 @@ void schedule(AstNetlist* netlistp) {
|
|||
// Remap sensitivities of the input logic to the triggers
|
||||
remapSensitivities(logicRegions.m_nba, nbaTrigMap);
|
||||
remapSensitivities(logicReplicas.m_nba, nbaTrigMap);
|
||||
const auto& nbaTimingDomains = timingKit.remapDomains(nbaTrigMap);
|
||||
|
||||
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
|
||||
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSenNba;
|
||||
|
@ -1196,7 +1228,9 @@ void schedule(AstNetlist* netlistp) {
|
|||
|
||||
AstCFunc* const nbaFuncp = V3Order::order(
|
||||
netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba",
|
||||
v3Global.opt.mtasks(), false, [=](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
||||
v3Global.opt.mtasks(), false, [&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
||||
auto it = nbaTimingDomains.find(vscp);
|
||||
if (it != nbaTimingDomains.end()) out = it->second;
|
||||
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredNba);
|
||||
});
|
||||
splitCheck(nbaFuncp);
|
||||
|
@ -1204,7 +1238,10 @@ void schedule(AstNetlist* netlistp) {
|
|||
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba");
|
||||
|
||||
// Step 11: Bolt it all together to create the '_eval' function
|
||||
createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp);
|
||||
createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp,
|
||||
timingKit);
|
||||
|
||||
transformForks(netlistp);
|
||||
|
||||
splitCheck(initp);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "V3Ast.h"
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
@ -113,6 +114,41 @@ struct LogicReplicas final {
|
|||
LogicReplicas& operator=(LogicReplicas&&) = default;
|
||||
};
|
||||
|
||||
// Everything needed for combining timing with static scheduling.
|
||||
class TimingKit final {
|
||||
AstCFunc* m_resumeFuncp = nullptr; // Global timing resume function
|
||||
AstCFunc* m_commitFuncp = nullptr; // Global timing commit function
|
||||
|
||||
// Additional var sensitivities for V3Order
|
||||
std::map<const AstVarScope*, std::set<AstSenTree*>> m_externalDomains;
|
||||
|
||||
public:
|
||||
LogicByScope m_lbs; // Actives that resume timing schedulers
|
||||
|
||||
// Remaps external domains using the specified trigger map
|
||||
std::map<const AstVarScope*, std::vector<AstSenTree*>>
|
||||
remapDomains(const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const;
|
||||
// Creates a timing resume call (if needed, else returns null)
|
||||
AstCCall* createResume(AstNetlist* const netlistp);
|
||||
// Creates a timing commit call (if needed, else returns null)
|
||||
AstCCall* createCommit(AstNetlist* const netlistp);
|
||||
|
||||
TimingKit() = default;
|
||||
TimingKit(LogicByScope&& lbs,
|
||||
std::map<const AstVarScope*, std::set<AstSenTree*>>&& externalDomains)
|
||||
: m_externalDomains{externalDomains}
|
||||
, m_lbs{lbs} {}
|
||||
VL_UNCOPYABLE(TimingKit);
|
||||
TimingKit(TimingKit&&) = default;
|
||||
TimingKit& operator=(TimingKit&&) = default;
|
||||
};
|
||||
|
||||
// Creates the timing kit and marks variables written by suspendables
|
||||
TimingKit prepareTiming(AstNetlist* const netlistp);
|
||||
|
||||
// Transforms fork sub-statements into separate functions
|
||||
void transformForks(AstNetlist* const netlistp);
|
||||
|
||||
// Top level entry point to scheduling
|
||||
void schedule(AstNetlist*);
|
||||
|
||||
|
|
|
@ -124,6 +124,10 @@ public:
|
|||
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()) {
|
||||
addDrivingRegions(INPUT);
|
||||
}
|
||||
// Currently we always execute suspendable processes at the beginning of
|
||||
// the act region, which means combinational logic driven from a suspendable
|
||||
// processes must be present in the 'act' region
|
||||
if (varp()->isWrittenBySuspendable()) addDrivingRegions(ACTIVE);
|
||||
}
|
||||
AstVarScope* vscp() const { return m_vscp; }
|
||||
AstVar* varp() const { return m_vscp->varp(); }
|
||||
|
|
|
@ -0,0 +1,361 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Code scheduling
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Functions defined in this file are used by V3Sched.cpp to properly integrate
|
||||
// static scheduling with timing features. They create external domains for
|
||||
// variables, remap them to trigger vectors, and create timing resume/commit
|
||||
// calls for the global eval loop. There is also a function that transforms
|
||||
// forks into emittable constructs.
|
||||
//
|
||||
// See the internals documentation docs/internals.rst for more details.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3EmitCBase.h"
|
||||
#include "V3Sched.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace V3Sched {
|
||||
|
||||
//============================================================================
|
||||
// Remaps external domains using the specified trigger map
|
||||
|
||||
std::map<const AstVarScope*, std::vector<AstSenTree*>>
|
||||
TimingKit::remapDomains(const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const {
|
||||
std::map<const AstVarScope*, std::vector<AstSenTree*>> remappedDomainMap;
|
||||
for (const auto& vscpDomains : m_externalDomains) {
|
||||
const AstVarScope* const vscp = vscpDomains.first;
|
||||
const auto& domains = vscpDomains.second;
|
||||
auto& remappedDomains = remappedDomainMap[vscp];
|
||||
remappedDomains.reserve(domains.size());
|
||||
for (AstSenTree* const domainp : domains) {
|
||||
remappedDomains.push_back(trigMap.at(domainp));
|
||||
}
|
||||
}
|
||||
return remappedDomainMap;
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
// Creates a timing resume call (if needed, else returns null)
|
||||
|
||||
AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
|
||||
if (!m_resumeFuncp) {
|
||||
if (m_lbs.empty()) return nullptr;
|
||||
// Create global resume function
|
||||
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
||||
m_resumeFuncp = new AstCFunc{netlistp->fileline(), "_timing_resume", scopeTopp, ""};
|
||||
m_resumeFuncp->dontCombine(true);
|
||||
m_resumeFuncp->isLoose(true);
|
||||
m_resumeFuncp->isConst(false);
|
||||
m_resumeFuncp->declPrivate(true);
|
||||
scopeTopp->addActivep(m_resumeFuncp);
|
||||
for (auto& p : m_lbs) {
|
||||
// Put all the timing actives in the resume function
|
||||
AstActive* const activep = p.second;
|
||||
m_resumeFuncp->addStmtsp(activep);
|
||||
}
|
||||
}
|
||||
return new AstCCall{m_resumeFuncp->fileline(), m_resumeFuncp};
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
// Creates a timing commit call (if needed, else returns null)
|
||||
|
||||
AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
|
||||
if (!m_commitFuncp) {
|
||||
for (auto& p : m_lbs) {
|
||||
AstActive* const activep = p.second;
|
||||
auto* const resumep = VN_AS(activep->stmtsp(), CMethodHard);
|
||||
UASSERT_OBJ(!resumep->nextp(), resumep, "Should be the only statement here");
|
||||
AstVarScope* const schedulerp = VN_AS(resumep->fromp(), VarRef)->varScopep();
|
||||
UASSERT_OBJ(schedulerp->dtypep()->basicp()->isDelayScheduler()
|
||||
|| schedulerp->dtypep()->basicp()->isTriggerScheduler(),
|
||||
schedulerp, "Unexpected type");
|
||||
if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue;
|
||||
// Create the global commit function only if we have trigger schedulers
|
||||
if (!m_commitFuncp) {
|
||||
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
||||
m_commitFuncp
|
||||
= new AstCFunc{netlistp->fileline(), "_timing_commit", scopeTopp, ""};
|
||||
m_commitFuncp->dontCombine(true);
|
||||
m_commitFuncp->isLoose(true);
|
||||
m_commitFuncp->isConst(false);
|
||||
m_commitFuncp->declPrivate(true);
|
||||
scopeTopp->addActivep(m_commitFuncp);
|
||||
}
|
||||
AstSenTree* const sensesp = activep->sensesp();
|
||||
FileLine* const flp = sensesp->fileline();
|
||||
// Negate the sensitivity. We will commit only if the event wasn't triggered on the
|
||||
// current iteration
|
||||
auto* const negSensesp = sensesp->cloneTree(false);
|
||||
negSensesp->sensesp()->sensp(
|
||||
new AstLogNot{flp, negSensesp->sensesp()->sensp()->unlinkFrBack()});
|
||||
sensesp->addNextHere(negSensesp);
|
||||
auto* const newactp = new AstActive{flp, "", negSensesp};
|
||||
// Create the commit call and put it in the commit function
|
||||
auto* const commitp = new AstCMethodHard{
|
||||
flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "commit"};
|
||||
commitp->addPinsp(resumep->pinsp()->cloneTree(false));
|
||||
commitp->statement(true);
|
||||
commitp->dtypeSetVoid();
|
||||
newactp->addStmtsp(commitp);
|
||||
m_commitFuncp->addStmtsp(newactp);
|
||||
}
|
||||
// We still haven't created a commit function (no trigger schedulers), return null
|
||||
if (!m_commitFuncp) return nullptr;
|
||||
}
|
||||
return new AstCCall{m_commitFuncp->fileline(), m_commitFuncp};
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
// Creates the timing kit and marks variables written by suspendables
|
||||
|
||||
TimingKit prepareTiming(AstNetlist* const netlistp) {
|
||||
if (!v3Global.usesTiming()) return {};
|
||||
class AwaitVisitor final : public VNVisitor {
|
||||
private:
|
||||
// NODE STATE
|
||||
// AstSenTree::user1() -> bool. Set true if the sentree has been visited.
|
||||
const VNUser1InUse m_inuser1;
|
||||
|
||||
// STATE
|
||||
bool m_inProcess = false; // Are we in a process?
|
||||
bool m_gatherVars = false; // Should we gather vars in m_writtenBySuspendable?
|
||||
AstScope* const m_scopeTopp; // Scope at the top
|
||||
LogicByScope& m_lbs; // Timing resume actives
|
||||
// Additional var sensitivities
|
||||
std::map<const AstVarScope*, std::set<AstSenTree*>>& m_externalDomains;
|
||||
std::set<AstSenTree*> m_processDomains; // Sentrees from the current process
|
||||
// Variables written by suspendable processes
|
||||
std::vector<AstVarScope*> m_writtenBySuspendable;
|
||||
|
||||
// METHODS
|
||||
// Create an active with a timing scheduler resume() call
|
||||
void createResumeActive(AstCAwait* const awaitp) {
|
||||
auto* const methodp = VN_AS(awaitp->exprp(), CMethodHard);
|
||||
AstVarScope* const schedulerp = VN_AS(methodp->fromp(), VarRef)->varScopep();
|
||||
AstSenTree* const sensesp = awaitp->sensesp();
|
||||
FileLine* const flp = sensesp->fileline();
|
||||
// Create a resume() call on the timing scheduler
|
||||
auto* const resumep = new AstCMethodHard{
|
||||
flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "resume"};
|
||||
if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) {
|
||||
resumep->addPinsp(methodp->pinsp()->cloneTree(false));
|
||||
}
|
||||
resumep->statement(true);
|
||||
resumep->dtypeSetVoid();
|
||||
// Put it in an active and put that in the global resume function
|
||||
auto* const activep = new AstActive{flp, "_timing", sensesp};
|
||||
activep->addStmtsp(resumep);
|
||||
m_lbs.emplace_back(m_scopeTopp, activep);
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstNodeProcedure* const nodep) override {
|
||||
UASSERT_OBJ(!m_inProcess && !m_gatherVars && m_processDomains.empty()
|
||||
&& m_writtenBySuspendable.empty(),
|
||||
nodep, "Process in process?");
|
||||
m_inProcess = true;
|
||||
m_gatherVars = nodep->isSuspendable(); // Only gather vars in a suspendable
|
||||
const VNUser2InUse user2InUse; // AstVarScope -> bool: Set true if var has been added
|
||||
// to m_writtenBySuspendable
|
||||
iterateChildren(nodep);
|
||||
for (AstVarScope* const vscp : m_writtenBySuspendable) {
|
||||
m_externalDomains[vscp].insert(m_processDomains.begin(), m_processDomains.end());
|
||||
vscp->varp()->setWrittenBySuspendable();
|
||||
}
|
||||
m_processDomains.clear();
|
||||
m_writtenBySuspendable.clear();
|
||||
m_inProcess = false;
|
||||
m_gatherVars = false;
|
||||
}
|
||||
virtual void visit(AstFork* nodep) override {
|
||||
VL_RESTORER(m_gatherVars);
|
||||
if (m_inProcess) m_gatherVars = true;
|
||||
// If not in a process, we don't need to gather variables or domains
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstCAwait* nodep) override {
|
||||
if (AstSenTree* const sensesp = nodep->sensesp()) {
|
||||
if (!sensesp->user1SetOnce()) createResumeActive(nodep);
|
||||
nodep->clearSensesp(); // Clear as these sentrees will get deleted later
|
||||
if (m_inProcess) m_processDomains.insert(sensesp);
|
||||
}
|
||||
}
|
||||
virtual void visit(AstNodeVarRef* nodep) override {
|
||||
if (m_gatherVars && nodep->access().isWriteOrRW()
|
||||
&& !nodep->varScopep()->user2SetOnce()) {
|
||||
m_writtenBySuspendable.push_back(nodep->varScopep());
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------
|
||||
virtual void visit(AstNodeMath*) override {} // Accelerate
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs,
|
||||
std::map<const AstVarScope*, std::set<AstSenTree*>>& externalDomains)
|
||||
: m_scopeTopp{nodep->topScopep()->scopep()}
|
||||
, m_lbs{lbs}
|
||||
, m_externalDomains{externalDomains} {
|
||||
iterate(nodep);
|
||||
}
|
||||
virtual ~AwaitVisitor() override = default;
|
||||
};
|
||||
LogicByScope lbs;
|
||||
std::map<const AstVarScope*, std::set<AstSenTree*>> externalDomains;
|
||||
AwaitVisitor{netlistp, lbs, externalDomains};
|
||||
return {std::move(lbs), std::move(externalDomains)};
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
// Visits all forks and transforms their sub-statements into separate functions.
|
||||
|
||||
void transformForks(AstNetlist* const netlistp) {
|
||||
if (!v3Global.usesTiming()) return;
|
||||
// Transform all forked processes into functions
|
||||
class ForkVisitor final : public VNVisitor {
|
||||
private:
|
||||
// NODE STATE
|
||||
// AstVar::user1() -> bool. Set true if the variable was declared before the current
|
||||
// fork.
|
||||
const VNUser1InUse m_inuser1;
|
||||
|
||||
// STATE
|
||||
bool m_inClass = false; // Are we in a class?
|
||||
AstFork* m_forkp = nullptr; // Current fork
|
||||
AstCFunc* m_funcp = nullptr; // Current function
|
||||
|
||||
// METHODS
|
||||
// Remap local vars referenced by the given fork function
|
||||
// TODO: We should only pass variables to the fork that are
|
||||
// live in the fork body, but for that we need a proper data
|
||||
// flow analysis framework which we don't have at the moment
|
||||
void remapLocals(AstCFunc* const funcp, AstCCall* const callp) {
|
||||
const VNUser2InUse user2InUse; // AstVarScope -> AstVarScope: var to remap to
|
||||
funcp->foreach<AstNodeVarRef>([&](AstNodeVarRef* refp) {
|
||||
AstVar* const varp = refp->varp();
|
||||
AstBasicDType* const dtypep = varp->dtypep()->basicp();
|
||||
// If it a fork sync or an intra-assignment variable, pass it by value
|
||||
const bool passByValue = (dtypep && dtypep->isForkSync())
|
||||
|| VString::startsWith(varp->name(), "__Vintra");
|
||||
// Only handle vars passed by value or locals declared before the fork
|
||||
if (!passByValue && (!varp->user1() || !varp->isFuncLocal())) return;
|
||||
if (passByValue) {
|
||||
// We can just pass it to the new function
|
||||
} else if (m_forkp->joinType().join()) {
|
||||
// If it's fork..join, we can refer to variables from the parent process
|
||||
if (m_funcp->user1SetOnce()) { // Only do this once per function
|
||||
// Move all locals to the heap before the fork
|
||||
auto* const awaitp = new AstCAwait{
|
||||
m_forkp->fileline(), new AstCStmt{m_forkp->fileline(), "VlNow{}"}};
|
||||
awaitp->statement(true);
|
||||
m_forkp->addHereThisAsNext(awaitp);
|
||||
}
|
||||
} else {
|
||||
refp->v3warn(E_UNSUPPORTED, "Unsupported: variable local to a forking process "
|
||||
"accessed in a fork..join_any or fork..join_none");
|
||||
return;
|
||||
}
|
||||
// Remap the reference
|
||||
AstVarScope* const vscp = refp->varScopep();
|
||||
if (!vscp->user2p()) {
|
||||
// Clone the var to the new function
|
||||
AstVar* const varp = refp->varp();
|
||||
AstVar* const newvarp
|
||||
= new AstVar{varp->fileline(), VVarType::BLOCKTEMP, varp->name(), varp};
|
||||
newvarp->funcLocal(true);
|
||||
newvarp->direction(passByValue ? VDirection::INPUT : VDirection::REF);
|
||||
funcp->addArgsp(newvarp);
|
||||
AstVarScope* const newvscp
|
||||
= new AstVarScope{newvarp->fileline(), funcp->scopep(), newvarp};
|
||||
funcp->scopep()->addVarp(newvscp);
|
||||
vscp->user2p(newvscp);
|
||||
callp->addArgsp(new AstVarRef{refp->fileline(), vscp, VAccess::READ});
|
||||
}
|
||||
auto* const newvscp = VN_AS(vscp->user2p(), VarScope);
|
||||
refp->varScopep(newvscp);
|
||||
refp->varp(newvscp->varp());
|
||||
});
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_inClass);
|
||||
m_inClass = VN_IS(nodep, Class);
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstCFunc* nodep) override {
|
||||
m_funcp = nodep;
|
||||
iterateChildren(nodep);
|
||||
m_funcp = nullptr;
|
||||
}
|
||||
virtual void visit(AstVar* nodep) override { nodep->user1(true); }
|
||||
virtual void visit(AstFork* nodep) override {
|
||||
VL_RESTORER(m_forkp);
|
||||
m_forkp = nodep;
|
||||
iterateChildrenConst(nodep); // Const, so we don't iterate the calls twice
|
||||
// Replace self with the function calls (no co_await, as we don't want the main
|
||||
// process to suspend whenever any of the children do)
|
||||
nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext());
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstBegin* nodep) override {
|
||||
UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork");
|
||||
UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name");
|
||||
FileLine* const flp = nodep->fileline();
|
||||
// Create a function to put this begin's statements in
|
||||
AstCFunc* const newfuncp
|
||||
= new AstCFunc{flp, nodep->name(), m_funcp->scopep(), "VlCoroutine"};
|
||||
m_funcp->addNextHere(newfuncp);
|
||||
newfuncp->isLoose(m_funcp->isLoose());
|
||||
newfuncp->slow(m_funcp->slow());
|
||||
newfuncp->isConst(m_funcp->isConst());
|
||||
newfuncp->declPrivate(true);
|
||||
// Replace the begin with a call to the newly created function
|
||||
auto* const callp = new AstCCall{flp, newfuncp};
|
||||
nodep->replaceWith(callp);
|
||||
// If we're in a class, add a vlSymsp arg
|
||||
if (m_inClass) {
|
||||
newfuncp->argTypes(EmitCBaseVisitor::symClassVar());
|
||||
callp->argTypes("vlSymsp");
|
||||
}
|
||||
// Put the begin's statements in the function, delete the begin
|
||||
newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext());
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
remapLocals(newfuncp, callp);
|
||||
}
|
||||
|
||||
//--------------------
|
||||
virtual void visit(AstNodeMath*) override {} // Accelerate
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit ForkVisitor(AstNetlist* nodep) { iterate(nodep); }
|
||||
virtual ~ForkVisitor() override = default;
|
||||
};
|
||||
ForkVisitor{netlistp};
|
||||
V3Global::dumpCheckGlobalTree("sched_forks", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6);
|
||||
}
|
||||
|
||||
} // namespace V3Sched
|
|
@ -428,6 +428,10 @@ protected:
|
|||
UINFO(9, " NotSplittable " << nodep << endl);
|
||||
scoreboardPli(nodep);
|
||||
}
|
||||
if (nodep->isTimingControl()) {
|
||||
UINFO(9, " NoReordering " << nodep << endl);
|
||||
m_noReorderWhy = "TimingControl";
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,659 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Prepare AST for timing features
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
// TimingVisitor transformations:
|
||||
// - for each intra-assignment timing control:
|
||||
// - if it's a continuous assignment, transform it into an always
|
||||
// - introduce an intermediate variable
|
||||
// - write the original RHS to the intermediate variable before the timing control
|
||||
// - write the intermediate variable to the original LHS after the timing control
|
||||
// - for each delay:
|
||||
// - scale it according to the module's timescale
|
||||
// - replace it with a CAwait statement waiting on the global delay scheduler (with the
|
||||
// specified delay value)
|
||||
// - if there is no global delay scheduler (see verilated_timing.{h,cpp}), create it
|
||||
// - for each event control:
|
||||
// - if there is no corresponding trigger scheduler (see verilated_timing.{h,cpp}), create it
|
||||
// - replace with a CAwait statement waiting on the corresponding trigger scheduler
|
||||
// - for each wait(cond) statement:
|
||||
// - replace it with a loop like: while (!cond) @(<vars from cond>)
|
||||
// - for each fork:
|
||||
// - put each statement in a begin if it isn't in one already
|
||||
// - if it's not a fork..join_none:
|
||||
// - create a join sync variable
|
||||
// - create statements that sync the main process with its children
|
||||
// - for each process or C++ function, if it has CAwait statements, mark it as suspendable
|
||||
// - if we mark a virtual function as suspendable, mark all overriding and overridden functions
|
||||
// as suspendable, as well as calling processes
|
||||
//
|
||||
// See the internals documentation docs/internals.rst for more details.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Timing.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3Const.h"
|
||||
#include "V3EmitV.h"
|
||||
#include "V3Graph.h"
|
||||
#include "V3SenTree.h"
|
||||
#include "V3UniqueNames.h"
|
||||
|
||||
//######################################################################
|
||||
// Transform nodes affected by timing
|
||||
|
||||
class TimingVisitor final : public VNVisitor {
|
||||
private:
|
||||
// TYPES
|
||||
// Vertex of a dependency graph of suspendable nodes, e.g. if a node (process or task) is
|
||||
// suspendable, all its dependents should also be suspendable
|
||||
class DependencyVertex final : public V3GraphVertex {
|
||||
AstNode* const m_nodep; // AST node represented by this graph vertex
|
||||
// ACCESSORS
|
||||
virtual string name() const override {
|
||||
return cvtToHex(nodep()) + ' ' + nodep()->prettyTypeName();
|
||||
}
|
||||
virtual FileLine* fileline() const override { return nodep()->fileline(); }
|
||||
virtual string dotColor() const override { return nodep()->user2() ? "red" : "black"; }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
DependencyVertex(V3Graph* graphp, AstNode* nodep)
|
||||
: V3GraphVertex{graphp}
|
||||
, m_nodep{nodep} {}
|
||||
virtual ~DependencyVertex() override = default;
|
||||
|
||||
// ACCESSORS
|
||||
virtual AstNode* nodep() const { return m_nodep; }
|
||||
};
|
||||
|
||||
// NODE STATE
|
||||
// AstNode::user1() -> bool. Set true if the node has been
|
||||
// processed.
|
||||
// AstSenTree::user1() -> AstVarScope*. Trigger scheduler assigned
|
||||
// to this sentree
|
||||
// Ast{NodeProcedure,CFunc,Begin}::user2() -> bool. Set true if process/task is
|
||||
// suspendable
|
||||
// AstSenTree::user2() -> AstText*. Debug info passed to the
|
||||
// timing schedulers
|
||||
// Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_depGraph
|
||||
const VNUser1InUse m_user1InUse;
|
||||
const VNUser2InUse m_user2InUse;
|
||||
const VNUser3InUse m_user3InUse;
|
||||
|
||||
// STATE
|
||||
// Current context
|
||||
AstNetlist* const m_netlistp; // Root node
|
||||
AstScope* const m_scopeTopp = m_netlistp->topScopep()->scopep(); // Scope at the top
|
||||
AstClass* m_classp = nullptr; // Current class
|
||||
AstScope* m_scopep = nullptr; // Current scope
|
||||
AstActive* m_activep = nullptr; // Current active
|
||||
AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Fork we're under
|
||||
double m_timescaleFactor; // Factor to scale delays by
|
||||
|
||||
// Unique names
|
||||
V3UniqueNames m_contAssignVarNames{"__VassignWtmp__"}; // Names for temp AssignW vars
|
||||
V3UniqueNames m_intraValueNames{"__Vintraval__"}; // Intra assign delay value var names
|
||||
V3UniqueNames m_intraIndexNames{"__Vintraidx__"}; // Intra assign delay index var names
|
||||
V3UniqueNames m_intraLsbNames{"__Vintralsb__"}; // Intra assign delay LSB var names
|
||||
V3UniqueNames m_forkNames{"__Vfork__"}; // Fork name generator
|
||||
V3UniqueNames m_trigSchedNames{"__VtrigSched"}; // Trigger scheduler name generator
|
||||
|
||||
// DTypes
|
||||
AstBasicDType* m_forkDtp = nullptr; // Fork variable type
|
||||
AstBasicDType* m_trigSchedDtp = nullptr; // Trigger scheduler type
|
||||
|
||||
// Timing-related globals
|
||||
AstVarScope* m_delaySchedp = nullptr; // Global delay scheduler
|
||||
AstSenTree* m_delaySensesp = nullptr; // Domain to trigger if a delayed coroutine is resumed
|
||||
|
||||
// Other
|
||||
V3Graph m_depGraph; // Dependency graph where a node is a dependency of another if it being
|
||||
// suspendable makes the other node suspendable
|
||||
SenTreeFinder m_finder{m_netlistp}; // Sentree finder and uniquifier
|
||||
|
||||
// METHODS
|
||||
VL_DEBUG_FUNC; // Declare debug()
|
||||
// Get or create the dependency vertex for the given node
|
||||
DependencyVertex* getDependencyVertex(AstNode* const nodep) {
|
||||
if (!nodep->user3p()) nodep->user3p(new DependencyVertex{&m_depGraph, nodep});
|
||||
return nodep->user3u().to<DependencyVertex*>();
|
||||
}
|
||||
// Find net delay on the LHS of an assignment
|
||||
AstNode* getLhsNetDelay(AstNodeAssign* nodep) const {
|
||||
bool foundWrite = false;
|
||||
AstNode* delayp = nullptr;
|
||||
nodep->lhsp()->foreach<AstNodeVarRef>([&](const AstNodeVarRef* const refp) {
|
||||
if (!refp->access().isWriteOrRW()) return;
|
||||
UASSERT_OBJ(!foundWrite, nodep, "Should only be one variable written to on the LHS");
|
||||
foundWrite = true;
|
||||
if (refp->varp()->delayp()) delayp = refp->varp()->delayp()->cloneTree(false);
|
||||
});
|
||||
return delayp;
|
||||
}
|
||||
// Transform an assignment with an intra timing control into a timing control with the
|
||||
// assignment under it
|
||||
AstNodeStmt* factorOutTimingControl(AstNodeAssign* nodep) const {
|
||||
AstNodeStmt* stmtp = nodep;
|
||||
AstNode* delayp = getLhsNetDelay(nodep);
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstNode* const controlp = nodep->timingControlp();
|
||||
if (controlp) {
|
||||
controlp->unlinkFrBack();
|
||||
if (!VN_IS(controlp, SenTree)) {
|
||||
delayp = delayp ? new AstAdd{flp, delayp, controlp} : controlp;
|
||||
}
|
||||
}
|
||||
if (delayp) {
|
||||
auto* const delayStmtp = new AstDelay{flp, delayp, nullptr};
|
||||
stmtp->replaceWith(delayStmtp);
|
||||
delayStmtp->stmtsp(stmtp);
|
||||
stmtp = delayStmtp;
|
||||
}
|
||||
if (auto* const sensesp = VN_CAST(controlp, SenTree)) {
|
||||
auto* const eventControlp = new AstEventControl{flp, sensesp, nullptr};
|
||||
stmtp->replaceWith(eventControlp);
|
||||
eventControlp->stmtsp(stmtp);
|
||||
stmtp = eventControlp;
|
||||
}
|
||||
return stmtp == nodep ? nullptr : stmtp;
|
||||
}
|
||||
// Calculate the factor to scale delays by
|
||||
double calculateTimescaleFactor(VTimescale timeunit) const {
|
||||
int scalePowerOfTen = timeunit.powerOfTen() - m_netlistp->timeprecision().powerOfTen();
|
||||
return std::pow(10.0, scalePowerOfTen);
|
||||
}
|
||||
// Construct SenItems from VarRefs in an expression
|
||||
AstSenItem* varRefpsToSenItemsp(AstNode* const nodep) const {
|
||||
AstNode* senItemsp = nullptr;
|
||||
const VNUser4InUse user4InUse;
|
||||
nodep->foreach<AstNodeVarRef>([&](AstNodeVarRef* refp) {
|
||||
if (refp->access().isWriteOnly()) return;
|
||||
AstVarScope* const vscp = refp->varScopep();
|
||||
if (vscp->user4SetOnce()) return;
|
||||
const bool isEvent = vscp->dtypep() && vscp->dtypep()->basicp()
|
||||
&& vscp->dtypep()->basicp()->isEvent();
|
||||
const auto edgeType = isEvent ? VEdgeType::ET_EVENT : VEdgeType::ET_CHANGED;
|
||||
senItemsp = AstNode::addNext(
|
||||
senItemsp, new AstSenItem{refp->fileline(), edgeType,
|
||||
new AstVarRef{refp->fileline(), vscp, VAccess::READ}});
|
||||
});
|
||||
return VN_AS(senItemsp, SenItem);
|
||||
}
|
||||
// Creates the global delay scheduler variable
|
||||
AstVarScope* getCreateDelayScheduler() {
|
||||
if (m_delaySchedp) return m_delaySchedp;
|
||||
auto* const dlySchedDtp = new AstBasicDType{
|
||||
m_scopeTopp->fileline(), VBasicDTypeKwd::DELAY_SCHEDULER, VSigning::UNSIGNED};
|
||||
m_netlistp->typeTablep()->addTypesp(dlySchedDtp);
|
||||
m_delaySchedp = m_scopeTopp->createTemp("__VdlySched", dlySchedDtp);
|
||||
// Delay scheduler has to be accessible from top
|
||||
m_delaySchedp->varp()->sigPublic(true);
|
||||
m_netlistp->delaySchedulerp(m_delaySchedp->varp());
|
||||
return m_delaySchedp;
|
||||
}
|
||||
// Creates the delay sentree
|
||||
AstSenTree* getCreateDelaySenTree() {
|
||||
if (m_delaySensesp) return m_delaySensesp;
|
||||
FileLine* const flp = m_scopeTopp->fileline();
|
||||
auto* const awaitingCurrentTimep
|
||||
= new AstCMethodHard{flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::READ},
|
||||
"awaitingCurrentTime"};
|
||||
awaitingCurrentTimep->dtypeSetBit();
|
||||
m_delaySensesp
|
||||
= new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}};
|
||||
m_netlistp->topScopep()->addSenTreep(m_delaySensesp);
|
||||
return m_delaySensesp;
|
||||
}
|
||||
// Creates a trigger scheduler variable
|
||||
AstVarScope* getCreateTriggerSchedulerp(AstSenTree* const sensesp) {
|
||||
if (!sensesp->user1p()) {
|
||||
if (!m_trigSchedDtp) {
|
||||
m_trigSchedDtp
|
||||
= new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::TRIGGER_SCHEDULER,
|
||||
VSigning::UNSIGNED};
|
||||
m_netlistp->typeTablep()->addTypesp(m_trigSchedDtp);
|
||||
}
|
||||
AstVarScope* const trigSchedp
|
||||
= m_scopeTopp->createTemp(m_trigSchedNames.get(sensesp), m_trigSchedDtp);
|
||||
sensesp->user1p(trigSchedp);
|
||||
}
|
||||
return VN_AS(sensesp->user1p(), VarScope);
|
||||
}
|
||||
// Creates a string describing the sentree
|
||||
AstText* createEventDescription(AstSenTree* const sensesp) const {
|
||||
if (!sensesp->user2p()) {
|
||||
std::stringstream ss;
|
||||
ss << '"';
|
||||
V3EmitV::verilogForTree(sensesp, ss);
|
||||
ss << '"';
|
||||
auto* const commentp = new AstText{sensesp->fileline(), ss.str()};
|
||||
sensesp->user2p(commentp);
|
||||
return commentp;
|
||||
}
|
||||
return VN_AS(sensesp->user2p(), Text)->cloneTree(false);
|
||||
}
|
||||
// Creates the fork handle type and returns it
|
||||
AstBasicDType* getCreateForkSyncDTypep() {
|
||||
if (m_forkDtp) return m_forkDtp;
|
||||
m_forkDtp = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::FORK_SYNC,
|
||||
VSigning::UNSIGNED};
|
||||
m_netlistp->typeTablep()->addTypesp(m_forkDtp);
|
||||
return m_forkDtp;
|
||||
}
|
||||
// Create a temp variable and optionally put it before the specified node (mark local if so)
|
||||
AstVarScope* createTemp(FileLine* const flp, const std::string& name,
|
||||
AstNodeDType* const dtypep, AstNode* const insertBeforep = nullptr) {
|
||||
AstVar* varp;
|
||||
if (insertBeforep) {
|
||||
varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
|
||||
varp->funcLocal(true);
|
||||
insertBeforep->addHereThisAsNext(varp);
|
||||
} else {
|
||||
varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
|
||||
m_scopep->modp()->addStmtp(varp);
|
||||
}
|
||||
AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp};
|
||||
m_scopep->addVarp(vscp);
|
||||
return vscp;
|
||||
}
|
||||
// Add a done() call on the fork sync
|
||||
void addForkDone(AstBegin* const beginp, AstVarScope* const forkVscp) const {
|
||||
FileLine* const flp = beginp->fileline();
|
||||
auto* const donep = new AstCMethodHard{
|
||||
beginp->fileline(), new AstVarRef{flp, forkVscp, VAccess::WRITE}, "done"};
|
||||
donep->dtypeSetVoid();
|
||||
donep->statement(true);
|
||||
// Add debug info
|
||||
donep->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
|
||||
donep->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
|
||||
beginp->addStmtsp(donep);
|
||||
}
|
||||
// Handle the 'join' part of a fork..join
|
||||
void makeForkJoin(AstFork* const forkp) {
|
||||
// Create a fork sync var
|
||||
FileLine* const flp = forkp->fileline();
|
||||
// If we're in a function, insert the sync var directly before the fork
|
||||
AstNode* const insertBeforep = VN_IS(m_procp, CFunc) ? forkp : nullptr;
|
||||
AstVarScope* forkVscp
|
||||
= createTemp(flp, forkp->name() + "__sync", getCreateForkSyncDTypep(), insertBeforep);
|
||||
unsigned joinCount = 0; // Needed for join counter
|
||||
// Add a <fork sync>.done() to each begin
|
||||
for (AstNode* beginp = forkp->stmtsp(); beginp; beginp = beginp->nextp()) {
|
||||
addForkDone(VN_AS(beginp, Begin), forkVscp);
|
||||
joinCount++;
|
||||
}
|
||||
if (forkp->joinType().joinAny()) joinCount = 1;
|
||||
// Set the join counter
|
||||
auto* const initp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE},
|
||||
"init", new AstConst{flp, joinCount}};
|
||||
initp->dtypeSetVoid();
|
||||
initp->statement(true);
|
||||
forkp->addHereThisAsNext(initp);
|
||||
// Await the join at the end
|
||||
auto* const joinp
|
||||
= new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"};
|
||||
joinp->dtypeSetVoid();
|
||||
// Add debug info
|
||||
joinp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
|
||||
joinp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
|
||||
auto* const awaitp = new AstCAwait{flp, joinp};
|
||||
awaitp->statement(true);
|
||||
forkp->addNextHere(awaitp);
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstNodeModule* nodep) override {
|
||||
UASSERT(!m_classp, "Module or class under class");
|
||||
VL_RESTORER(m_classp);
|
||||
m_classp = VN_CAST(nodep, Class);
|
||||
VL_RESTORER(m_timescaleFactor);
|
||||
m_timescaleFactor = calculateTimescaleFactor(nodep->timeunit());
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstScope* nodep) override {
|
||||
VL_RESTORER(m_scopep);
|
||||
m_scopep = nodep;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstActive* nodep) override {
|
||||
m_activep = nodep;
|
||||
iterateChildren(nodep);
|
||||
m_activep = nullptr;
|
||||
}
|
||||
virtual void visit(AstNodeProcedure* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
iterateChildren(nodep);
|
||||
if (nodep->user2()) nodep->setSuspendable();
|
||||
}
|
||||
virtual void visit(AstAlways* nodep) override {
|
||||
visit(static_cast<AstNodeProcedure*>(nodep));
|
||||
if (nodep->isSuspendable() && !nodep->user1SetOnce()) {
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstSenTree* const sensesp = m_activep->sensesp();
|
||||
if (sensesp->hasClocked()) {
|
||||
AstNode* bodysp = nodep->bodysp()->unlinkFrBackWithNext();
|
||||
auto* const controlp = new AstEventControl{flp, sensesp->cloneTree(false), bodysp};
|
||||
nodep->addStmtp(controlp);
|
||||
iterate(controlp);
|
||||
}
|
||||
// Note: The 'while (true)' outer loop will be added in V3Sched
|
||||
auto* const activep = new AstActive{
|
||||
flp, "", new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}};
|
||||
activep->sensesStorep(activep->sensesp());
|
||||
activep->addStmtsp(nodep->unlinkFrBack());
|
||||
m_activep->addNextHere(activep);
|
||||
}
|
||||
}
|
||||
virtual void visit(AstCFunc* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
iterateChildren(nodep);
|
||||
DependencyVertex* const vxp = getDependencyVertex(nodep);
|
||||
if (m_classp && nodep->isVirtual()
|
||||
&& !nodep->user1SetOnce()) { // If virtual (only visit once)
|
||||
// Go over overridden functions
|
||||
m_classp->repairCache();
|
||||
for (auto* cextp = m_classp->extendsp(); cextp;
|
||||
cextp = VN_AS(cextp->nextp(), ClassExtends)) {
|
||||
if (auto* const overriddenp
|
||||
= VN_CAST(cextp->classp()->findMember(nodep->name()), CFunc)) {
|
||||
if (overriddenp->user2()) { // If suspendable
|
||||
if (!nodep->user2()) {
|
||||
// It should be a coroutine but it has no awaits. Add a co_return at
|
||||
// the end (either that or a co_await is required in a coroutine)
|
||||
nodep->addStmtsp(new AstCStmt{nodep->fileline(), "co_return;\n"});
|
||||
}
|
||||
nodep->user2(true);
|
||||
// If it's suspendable already, no need to add it as our dependency or
|
||||
// self to its dependencies
|
||||
} else {
|
||||
DependencyVertex* const overriddenVxp = getDependencyVertex(overriddenp);
|
||||
new V3GraphEdge{&m_depGraph, vxp, overriddenVxp, 1};
|
||||
new V3GraphEdge{&m_depGraph, overriddenVxp, vxp, 1};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodep->user2() && !nodep->isCoroutine()) { // If first marked as suspendable
|
||||
nodep->rtnType("VlCoroutine");
|
||||
// Revisit dependent nodes if needed
|
||||
for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
auto* const depVxp = static_cast<DependencyVertex*>(edgep->fromp());
|
||||
AstNode* const depp = depVxp->nodep();
|
||||
if (!depp->user2()) { // If dependent not suspendable
|
||||
depp->user2(true);
|
||||
if (auto* const funcp = VN_CAST(depp, CFunc)) {
|
||||
// It's a coroutine but has no awaits (a class method that overrides/is
|
||||
// overridden by a suspendable, but doesn't have any awaits itself). Add a
|
||||
// co_return at the end (either that or a co_await is required in a
|
||||
// coroutine)
|
||||
funcp->addStmtsp(new AstCStmt{funcp->fileline(), "co_return;\n"});
|
||||
}
|
||||
}
|
||||
iterate(depp);
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual void visit(AstNodeCCall* nodep) override {
|
||||
if (nodep->funcp()->user2()) { // If suspendable
|
||||
auto* const awaitp = new AstCAwait{nodep->fileline(), nullptr};
|
||||
nodep->replaceWith(awaitp);
|
||||
awaitp->exprp(nodep);
|
||||
} else {
|
||||
// Add our process/func as the CFunc's dependency as we might have to put an await here
|
||||
DependencyVertex* const procVxp = getDependencyVertex(m_procp);
|
||||
DependencyVertex* const funcVxp = getDependencyVertex(nodep->funcp());
|
||||
new V3GraphEdge{&m_depGraph, procVxp, funcVxp, 1};
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstCAwait* nodep) override {
|
||||
v3Global.setUsesTiming();
|
||||
m_procp->user2(true);
|
||||
}
|
||||
virtual void visit(AstDelay* nodep) override {
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstNode* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
|
||||
auto* const constp = VN_CAST(valuep, Const);
|
||||
if (constp && constp->isZero()) {
|
||||
nodep->v3warn(ZERODLY, "Unsupported: #0 delays do not schedule process resumption in "
|
||||
"the Inactive region");
|
||||
} else {
|
||||
// Scale the delay
|
||||
if (valuep->dtypep()->isDouble()) {
|
||||
valuep = new AstRToIRoundS{
|
||||
flp,
|
||||
new AstMulD{flp, valuep,
|
||||
new AstConst{flp, AstConst::RealDouble{}, m_timescaleFactor}}};
|
||||
} else {
|
||||
valuep = new AstMul{flp, valuep,
|
||||
new AstConst{flp, AstConst::Unsized64(),
|
||||
static_cast<uint64_t>(m_timescaleFactor)}};
|
||||
}
|
||||
}
|
||||
// Replace self with a 'co_await dlySched.delay(<valuep>)'
|
||||
auto* const delayMethodp = new AstCMethodHard{
|
||||
flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, "delay", valuep};
|
||||
delayMethodp->dtypeSetVoid();
|
||||
// Add debug info
|
||||
delayMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
|
||||
delayMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
|
||||
// Create the co_await
|
||||
auto* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()};
|
||||
awaitp->statement(true);
|
||||
// Relink child statements after the co_await
|
||||
if (nodep->stmtsp()) awaitp->addNext(nodep->stmtsp()->unlinkFrBackWithNext());
|
||||
nodep->replaceWith(awaitp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstEventControl* nodep) override {
|
||||
if (m_classp) nodep->v3warn(E_UNSUPPORTED, "Unsupported: event controls in methods");
|
||||
auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
|
||||
nodep->sensesp()->unlinkFrBack()->deleteTree();
|
||||
// Get this sentree's trigger scheduler
|
||||
FileLine* const flp = nodep->fileline();
|
||||
// Replace self with a 'co_await trigSched.trigger()'
|
||||
auto* const triggerMethodp = new AstCMethodHard{
|
||||
flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
|
||||
"trigger"};
|
||||
triggerMethodp->dtypeSetVoid();
|
||||
// Add debug info
|
||||
triggerMethodp->addPinsp(createEventDescription(sensesp));
|
||||
triggerMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
|
||||
triggerMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
|
||||
// Create the co_await
|
||||
auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
|
||||
awaitp->statement(true);
|
||||
// Relink child statements after the co_await
|
||||
if (nodep->stmtsp()) awaitp->addNext(nodep->stmtsp()->unlinkFrBackWithNext());
|
||||
nodep->replaceWith(awaitp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
// Only process once to avoid infinite loops (due to the net delay)
|
||||
if (nodep->user1SetOnce()) return;
|
||||
AstNode* const controlp = factorOutTimingControl(nodep);
|
||||
if (!controlp) return;
|
||||
// Handle the intra assignment timing control
|
||||
FileLine* const flp = nodep->fileline();
|
||||
if (VN_IS(nodep, AssignDly)) {
|
||||
// If it's an NBA with an intra assignment delay, put it in a fork
|
||||
auto* const forkp = new AstFork{flp, "", nullptr};
|
||||
forkp->joinType(VJoinType::JOIN_NONE);
|
||||
controlp->replaceWith(forkp);
|
||||
forkp->addStmtsp(controlp);
|
||||
}
|
||||
// Insert new vars before the timing control if we're in a function; in a process we can't
|
||||
// do that. These intra-assignment vars will later be passed to forked processes by value.
|
||||
AstNode* const insertBeforep = VN_IS(m_procp, CFunc) ? controlp : nullptr;
|
||||
// Function for replacing values with intermediate variables
|
||||
const auto replaceWithIntermediate = [&](AstNode* const valuep, const std::string& name) {
|
||||
AstVarScope* const newvscp = createTemp(flp, name, valuep->dtypep(), insertBeforep);
|
||||
valuep->replaceWith(new AstVarRef{flp, newvscp, VAccess::READ});
|
||||
controlp->addHereThisAsNext(
|
||||
new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, valuep});
|
||||
};
|
||||
// Create the intermediate select vars. Note: because 'foreach' proceeds in
|
||||
// pre-order, and we replace indices in selects with variables, we cannot
|
||||
// reach another select under the index position. This is exactly what
|
||||
// we want as only the top level selects are LValues. As an example,
|
||||
// this transforms 'x[a[i]][b[j]] = y'
|
||||
// into 't1 = a[i]; t0 = b[j]; x[t1][t0] = y'.
|
||||
nodep->lhsp()->foreach<AstSel>([&](AstSel* selp) {
|
||||
if (VN_IS(selp->lsbp(), Const)) return;
|
||||
replaceWithIntermediate(selp->lsbp(), m_intraLsbNames.get(nodep));
|
||||
// widthp should be const
|
||||
});
|
||||
nodep->lhsp()->foreach<AstNodeSel>([&](AstNodeSel* selp) {
|
||||
if (VN_IS(selp->bitp(), Const)) return;
|
||||
replaceWithIntermediate(selp->bitp(), m_intraIndexNames.get(nodep));
|
||||
});
|
||||
// Replace the RHS with an intermediate value var
|
||||
replaceWithIntermediate(nodep->rhsp(), m_intraValueNames.get(nodep));
|
||||
}
|
||||
virtual void visit(AstAssignW* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
auto* const netDelayp = getLhsNetDelay(nodep);
|
||||
if (!netDelayp && !nodep->timingControlp()) return;
|
||||
// This assignment will be converted to an always. In some cases this may generate an
|
||||
// UNOPTFLAT, e.g.: assign #1 clk = ~clk. We create a temp var for the LHS of this assign,
|
||||
// to disable the UNOPTFLAT warning for it.
|
||||
// TODO: Find a way to do this without introducing this var. Perhaps make V3SchedAcyclic
|
||||
// recognize awaits and prevent it from treating this kind of logic as cyclic
|
||||
AstNode* const lhsp = nodep->lhsp()->unlinkFrBack();
|
||||
std::string varname;
|
||||
if (auto* const refp = VN_CAST(lhsp, VarRef)) {
|
||||
varname = m_contAssignVarNames.get(refp->name());
|
||||
} else {
|
||||
varname = m_contAssignVarNames.get(lhsp);
|
||||
}
|
||||
auto* const tempvscp = m_scopep->createTemp(varname, lhsp->dtypep());
|
||||
tempvscp->varp()->delayp(netDelayp);
|
||||
FileLine* const flp = nodep->fileline();
|
||||
flp->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true);
|
||||
tempvscp->fileline(flp);
|
||||
tempvscp->varp()->fileline(flp);
|
||||
// Remap the LHS to the new temp var
|
||||
nodep->lhsp(new AstVarRef{flp, tempvscp, VAccess::WRITE});
|
||||
// Convert it to an always; the new assign with intra delay will be handled by
|
||||
// visit(AstNodeAssign*)
|
||||
AstAlways* const alwaysp = nodep->convertToAlways();
|
||||
visit(alwaysp);
|
||||
// Put the LHS back in the AssignW; put the temp var on the RHS
|
||||
nodep->lhsp(lhsp);
|
||||
nodep->rhsp(new AstVarRef{flp, tempvscp, VAccess::READ});
|
||||
// Put the AssignW right after the always. Different order can produce UNOPTFLAT on the LHS
|
||||
// var
|
||||
alwaysp->addNextHere(nodep);
|
||||
}
|
||||
virtual void visit(AstWait* nodep) override {
|
||||
// Wait on changed events related to the vars in the wait statement
|
||||
AstSenItem* const senItemsp = varRefpsToSenItemsp(nodep->condp());
|
||||
AstNode* const condp = nodep->condp()->unlinkFrBack();
|
||||
AstNode* const bodysp = nodep->bodysp();
|
||||
if (bodysp) bodysp->unlinkFrBackWithNext();
|
||||
FileLine* const flp = nodep->fileline();
|
||||
if (senItemsp) {
|
||||
// Put the event control in a while loop with the wait expression as condition
|
||||
auto* const loopp
|
||||
= new AstWhile{flp, new AstLogNot{flp, condp},
|
||||
new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}};
|
||||
loopp->addNextNull(bodysp);
|
||||
nodep->replaceWith(loopp);
|
||||
} else {
|
||||
condp->v3warn(WAITCONST, "Wait statement condition is constant");
|
||||
auto* constCondp = VN_AS(V3Const::constifyEdit(condp), Const);
|
||||
if (constCondp->isZero()) {
|
||||
// We have to await forever instead of simply returning in case we're deep in a
|
||||
// callstack
|
||||
auto* const awaitp = new AstCAwait{flp, new AstCStmt{flp, "VlForever{}"}};
|
||||
awaitp->statement(true);
|
||||
nodep->replaceWith(awaitp);
|
||||
if (bodysp) VL_DO_DANGLING(bodysp->deleteTree(), bodysp);
|
||||
} else if (bodysp) {
|
||||
// Just put the body there
|
||||
nodep->replaceWith(bodysp);
|
||||
}
|
||||
VL_DO_DANGLING(constCondp->deleteTree(), condp);
|
||||
}
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstFork* nodep) override {
|
||||
if (nodep->user1SetOnce()) return;
|
||||
// Create a unique name for this fork
|
||||
nodep->name(m_forkNames.get(nodep));
|
||||
unsigned idx = 0; // Index for naming begins
|
||||
AstNode* stmtp = nodep->stmtsp();
|
||||
// Put each statement in a begin
|
||||
while (stmtp) {
|
||||
if (!VN_IS(stmtp, Begin)) {
|
||||
auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr};
|
||||
stmtp->replaceWith(beginp);
|
||||
beginp->addStmtsp(stmtp);
|
||||
stmtp = beginp;
|
||||
}
|
||||
auto* const beginp = VN_AS(stmtp, Begin);
|
||||
stmtp = beginp->nextp();
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = beginp;
|
||||
iterate(beginp);
|
||||
if (!m_procp->user2()) {
|
||||
// No awaits, we can inline this process
|
||||
if (auto* const stmtsp = beginp->stmtsp()) {
|
||||
nodep->addHereThisAsNext(stmtsp->unlinkFrBackWithNext());
|
||||
}
|
||||
VL_DO_DANGLING(beginp->unlinkFrBack()->deleteTree(), beginp);
|
||||
// We inlined at least one process, so we can consider it joined; convert join_any
|
||||
// to join_none
|
||||
if (nodep->joinType().joinAny()) nodep->joinType(VJoinType::JOIN_NONE);
|
||||
} else {
|
||||
// Name the begin (later the name will be used for a new function)
|
||||
beginp->name(nodep->name() + "__" + cvtToStr(idx++));
|
||||
}
|
||||
}
|
||||
if (!nodep->stmtsp()) {
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (!nodep->joinType().joinNone()) {
|
||||
makeForkJoin(nodep);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------
|
||||
virtual void visit(AstNodeMath*) override {} // Accelerate
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit TimingVisitor(AstNetlist* nodep)
|
||||
: m_netlistp{nodep} {
|
||||
iterate(nodep);
|
||||
if (debug() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps");
|
||||
}
|
||||
virtual ~TimingVisitor() override = default;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Timing class functions
|
||||
|
||||
void V3Timing::timingAll(AstNetlist* nodep) {
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
TimingVisitor{nodep};
|
||||
V3Global::dumpCheckGlobalTree("timing", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Prepare AST for features features
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#ifndef _V3TIMING_H_
|
||||
#define _V3TIMING_H_
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
|
||||
//============================================================================
|
||||
|
||||
class V3Timing final {
|
||||
public:
|
||||
static void timingAll(AstNetlist* nodep);
|
||||
};
|
||||
|
||||
#endif // Guard
|
|
@ -57,6 +57,7 @@ private:
|
|||
AstNodeModule* m_modp = nullptr; // Current module
|
||||
AstAssignW* m_assignwp = nullptr; // Current assignment
|
||||
AstAssignDly* m_assigndlyp = nullptr; // Current assignment
|
||||
AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control
|
||||
bool m_constXCvt = false; // Convert X's
|
||||
bool m_allowXUnique = true; // Allow unique assignments
|
||||
VDouble0 m_statUnkVars; // Statistic tracking
|
||||
|
@ -123,12 +124,14 @@ private:
|
|||
m_modp->addStmtp(varp);
|
||||
AstNode* const abovep = prep->backp(); // Grab above point before we replace 'prep'
|
||||
prep->replaceWith(new AstVarRef(fl, varp, VAccess::WRITE));
|
||||
if (m_timingControlp) m_timingControlp->unlinkFrBack();
|
||||
AstIf* const newp = new AstIf(
|
||||
fl, condp,
|
||||
(needDly ? static_cast<AstNode*>(
|
||||
new AstAssignDly(fl, prep, new AstVarRef(fl, varp, VAccess::READ)))
|
||||
: static_cast<AstNode*>(
|
||||
new AstAssign(fl, prep, new AstVarRef(fl, varp, VAccess::READ)))));
|
||||
(needDly
|
||||
? static_cast<AstNode*>(new AstAssignDly{
|
||||
fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp})
|
||||
: static_cast<AstNode*>(new AstAssign{
|
||||
fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp})));
|
||||
newp->branchPred(VBranchPred::BP_LIKELY);
|
||||
newp->isBoundsCheck(true);
|
||||
if (debug() >= 9) newp->dumpTree(cout, " _new: ");
|
||||
|
@ -155,18 +158,29 @@ private:
|
|||
}
|
||||
virtual void visit(AstAssignDly* nodep) override {
|
||||
VL_RESTORER(m_assigndlyp);
|
||||
VL_RESTORER(m_timingControlp);
|
||||
{
|
||||
m_assigndlyp = nodep;
|
||||
m_timingControlp = nodep->timingControlp();
|
||||
VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep.
|
||||
}
|
||||
}
|
||||
virtual void visit(AstAssignW* nodep) override {
|
||||
VL_RESTORER(m_assignwp);
|
||||
VL_RESTORER(m_timingControlp);
|
||||
{
|
||||
m_assignwp = nodep;
|
||||
m_timingControlp = nodep->timingControlp();
|
||||
VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep.
|
||||
}
|
||||
}
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
VL_RESTORER(m_timingControlp);
|
||||
{
|
||||
m_timingControlp = nodep->timingControlp();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
}
|
||||
virtual void visit(AstCaseItem* nodep) override {
|
||||
VL_RESTORER(m_constXCvt);
|
||||
{
|
||||
|
|
111
src/V3Width.cpp
111
src/V3Width.cpp
|
@ -607,8 +607,20 @@ private:
|
|||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
return;
|
||||
}
|
||||
if (nodep->fileline()->timingOn()) {
|
||||
if (v3Global.opt.timing().isSetTrue()) {
|
||||
userIterate(nodep->lhsp(), WidthVP{nullptr, BOTH}.p());
|
||||
iterateNull(nodep->stmtsp());
|
||||
return;
|
||||
} else if (v3Global.opt.timing().isSetFalse()) {
|
||||
nodep->v3warn(STMTDLY, "Ignoring delay on this statement due to --no-timing");
|
||||
} else {
|
||||
nodep->v3warn(
|
||||
E_NEEDTIMINGOPT,
|
||||
"Use --timing or --no-timing to specify how delays should be handled");
|
||||
}
|
||||
}
|
||||
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack());
|
||||
nodep->v3warn(STMTDLY, "Unsupported: Ignoring delay on this delayed statement.");
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
}
|
||||
virtual void visit(AstFork* nodep) override {
|
||||
|
@ -618,19 +630,27 @@ private:
|
|||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
return;
|
||||
}
|
||||
if (v3Global.opt.bboxUnsup()
|
||||
if (!nodep->fileline()->timingOn()
|
||||
// With no statements, begin is identical
|
||||
|| !nodep->stmtsp()
|
||||
// With one statement, a begin block does as good as a fork/join or join_any
|
||||
|| (!nodep->stmtsp()->nextp() && !nodep->joinType().joinNone())) {
|
||||
|| (!v3Global.opt.timing().isSetTrue() // If no --timing
|
||||
&& (v3Global.opt.bboxUnsup()
|
||||
// With one statement and no timing, a begin block does as good as a
|
||||
// fork/join or join_any
|
||||
|| (!nodep->stmtsp()->nextp() && !nodep->joinType().joinNone())))) {
|
||||
AstNode* stmtsp = nullptr;
|
||||
if (nodep->stmtsp()) stmtsp = nodep->stmtsp()->unlinkFrBack();
|
||||
AstBegin* const newp = new AstBegin{nodep->fileline(), nodep->name(), stmtsp};
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
} else if (v3Global.opt.timing().isSetTrue()) {
|
||||
iterateChildren(nodep);
|
||||
} else if (v3Global.opt.timing().isSetFalse()) {
|
||||
nodep->v3warn(E_NOTIMING, "Fork statements require --timing");
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
} else {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: fork statements");
|
||||
// TBD might support only normal join, if so complain about other join flavors
|
||||
nodep->v3warn(E_NEEDTIMINGOPT,
|
||||
"Use --timing or --no-timing to specify how forks should be handled");
|
||||
}
|
||||
}
|
||||
virtual void visit(AstDisableFork* nodep) override {
|
||||
|
@ -1358,10 +1378,28 @@ private:
|
|||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstEventControl* nodep) override {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: event control statement in this location\n"
|
||||
<< nodep->warnMore()
|
||||
<< "... Suggest have one event control statement "
|
||||
<< "per procedure, at the top of the procedure");
|
||||
if (VN_IS(m_ftaskp, Func)) {
|
||||
nodep->v3error("Event controls are not legal in functions. Suggest use a task "
|
||||
"(IEEE 1800-2017 13.4.4)");
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
return;
|
||||
}
|
||||
if (nodep->fileline()->timingOn()) {
|
||||
if (v3Global.opt.timing().isSetTrue()) {
|
||||
iterateChildren(nodep);
|
||||
return;
|
||||
} else if (v3Global.opt.timing().isSetFalse()) {
|
||||
nodep->v3warn(E_NOTIMING,
|
||||
"Event control statement in this location requires --timing\n"
|
||||
<< nodep->warnMore()
|
||||
<< "... With --no-timing, suggest have one event control "
|
||||
<< "statement per procedure, at the top of the procedure");
|
||||
} else {
|
||||
nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how "
|
||||
"event controls should be handled");
|
||||
}
|
||||
}
|
||||
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack());
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstAttrOf* nodep) override {
|
||||
|
@ -4236,10 +4274,26 @@ private:
|
|||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type");
|
||||
}
|
||||
}
|
||||
if (nodep->timingControlp()) {
|
||||
nodep->timingControlp()->v3warn(
|
||||
ASSIGNDLY, "Unsupported: Ignoring timing control on this assignment.");
|
||||
nodep->timingControlp()->unlinkFrBackWithNext()->deleteTree();
|
||||
if (auto* const controlp = nodep->timingControlp()) {
|
||||
if (VN_IS(m_ftaskp, Func)) {
|
||||
controlp->v3error("Timing controls are not legal in functions. Suggest use a task "
|
||||
"(IEEE 1800-2017 13.4.4)");
|
||||
VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp);
|
||||
} else if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) {
|
||||
iterateNull(controlp);
|
||||
} else {
|
||||
if (nodep->fileline()->timingOn()) {
|
||||
if (v3Global.opt.timing().isSetFalse()) {
|
||||
controlp->v3warn(ASSIGNDLY, "Ignoring timing control on this "
|
||||
"assignment/primitive due to --no-timing");
|
||||
} else {
|
||||
controlp->v3warn(E_NEEDTIMINGOPT,
|
||||
"Use --timing or --no-timing to specify how "
|
||||
"timing controls should be handled");
|
||||
}
|
||||
}
|
||||
VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp);
|
||||
}
|
||||
}
|
||||
if (VN_IS(nodep->rhsp(), EmptyQueue)) {
|
||||
UINFO(9, "= {} -> .delete(): " << nodep);
|
||||
|
@ -5067,6 +5121,35 @@ private:
|
|||
userIterateChildren(nodep, WidthVP(SELF, BOTH).p());
|
||||
}
|
||||
}
|
||||
virtual void visit(AstWait* nodep) override {
|
||||
if (VN_IS(m_ftaskp, Func)) {
|
||||
nodep->v3error("Wait statements are not legal in functions. Suggest use a task "
|
||||
"(IEEE 1800-2017 13.4.4)");
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
return;
|
||||
}
|
||||
if (nodep->fileline()->timingOn()) {
|
||||
if (v3Global.opt.timing().isSetTrue()) {
|
||||
userIterate(nodep->condp(), WidthVP(SELF, PRELIM).p());
|
||||
iterateNull(nodep->bodysp());
|
||||
return;
|
||||
} else if (v3Global.opt.timing().isSetFalse()) {
|
||||
nodep->v3warn(E_NOTIMING, "Wait statements require --timing");
|
||||
} else {
|
||||
nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how wait "
|
||||
"statements should be handled");
|
||||
}
|
||||
}
|
||||
// If we ignore timing:
|
||||
// Statements we'll just execute immediately; equivalent to if they followed this
|
||||
if (AstNode* const bodysp = nodep->bodysp()) {
|
||||
bodysp->unlinkFrBackWithNext();
|
||||
nodep->replaceWith(bodysp);
|
||||
} else {
|
||||
nodep->unlinkFrBack();
|
||||
}
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
virtual void visit(AstWith* nodep) override {
|
||||
// Should otherwise be underneath a method call
|
||||
AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp();
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
#include "V3TSP.h"
|
||||
#include "V3Table.h"
|
||||
#include "V3Task.h"
|
||||
#include "V3Timing.h"
|
||||
#include "V3Trace.h"
|
||||
#include "V3TraceDecl.h"
|
||||
#include "V3Tristate.h"
|
||||
|
@ -362,6 +363,14 @@ static void process() {
|
|||
// Reorder assignments in pipelined blocks
|
||||
if (v3Global.opt.fReorder()) V3Split::splitReorderAll(v3Global.rootp());
|
||||
|
||||
if (v3Global.opt.timing().isSetTrue()) {
|
||||
// Convert AST for timing if requested
|
||||
// Needs to be after V3Gate, as that step modifies sentrees
|
||||
// Needs to be before V3Delayed, as delayed assignments are handled differently in
|
||||
// suspendable processes
|
||||
V3Timing::timingAll(v3Global.rootp());
|
||||
}
|
||||
|
||||
// Create delayed assignments
|
||||
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step
|
||||
V3Delayed::delayedAll(v3Global.rootp());
|
||||
|
|
|
@ -82,6 +82,7 @@ using std::endl;
|
|||
// - If defined, the default search path has it, so support is always enabled.
|
||||
// - If undef, not system-wide, user can set SYSTEMC_INCLUDE.
|
||||
#undef HAVE_SYSTEMC
|
||||
#undef HAVE_COROUTINES
|
||||
|
||||
//**********************************************************************
|
||||
//**** OS and compiler specifics
|
||||
|
|
|
@ -131,6 +131,8 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
|
|||
"sc_bv" { FL; return yVLT_SC_BV; }
|
||||
"sformat" { FL; return yVLT_SFORMAT; }
|
||||
"split_var" { FL; return yVLT_SPLIT_VAR; }
|
||||
"timing_off" { FL; return yVLT_TIMING_OFF; }
|
||||
"timing_on" { FL; return yVLT_TIMING_ON; }
|
||||
"tracing_off" { FL; return yVLT_TRACING_OFF; }
|
||||
"tracing_on" { FL; return yVLT_TRACING_ON; }
|
||||
|
||||
|
@ -748,6 +750,8 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
|
|||
"/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; }
|
||||
"/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext));
|
||||
return yVL_TAG; }
|
||||
"/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; }
|
||||
"/*verilator timing_on*/" { FL_FWD; PARSEP->lexFileline()->timingOn(true); FL_BRK; }
|
||||
"/*verilator trace_init_task*/" { FL; return yVL_TRACE_INIT_TASK; }
|
||||
"/*verilator tracing_off*/" { FL_FWD; PARSEP->lexFileline()->tracingOn(false); FL_BRK; }
|
||||
"/*verilator tracing_on*/" { FL_FWD; PARSEP->lexFileline()->tracingOn(true); FL_BRK; }
|
||||
|
|
129
src/verilog.y
129
src/verilog.y
|
@ -40,11 +40,22 @@
|
|||
#define BBUNSUP(fl, msg) (fl)->v3warn(E_UNSUPPORTED, msg)
|
||||
#define GATEUNSUP(fl, tok) \
|
||||
{ BBUNSUP((fl), "Unsupported: Verilog 1995 gate primitive: " << (tok)); }
|
||||
#define PRIMDLYUNSUP(nodep) \
|
||||
{ \
|
||||
if (nodep) { \
|
||||
nodep->v3warn(ASSIGNDLY, "Unsupported: Ignoring delay on this primitive."); \
|
||||
nodep->deleteTree(); \
|
||||
#define RISEFALLDLYUNSUP(nodep) \
|
||||
if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \
|
||||
nodep->v3warn(RISEFALLDLY, \
|
||||
"Unsupported: rising/falling/turn-off delays. Using the first delay"); \
|
||||
}
|
||||
#define MINTYPMAXDLYUNSUP(nodep) \
|
||||
if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \
|
||||
nodep->v3warn( \
|
||||
MINTYPMAXDLY, \
|
||||
"Unsupported: minimum/typical/maximum delay expressions. Using the typical delay"); \
|
||||
}
|
||||
#define PUT_DLYS_IN_ASSIGNS(delayp, assignsp) \
|
||||
if (delayp) { \
|
||||
for (auto* nodep = assignsp; nodep; nodep = nodep->nextp()) { \
|
||||
auto* const assignp = VN_AS(nodep, NodeAssign); \
|
||||
assignp->timingControlp(nodep == assignsp ? delayp : delayp->cloneTree(false)); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
@ -393,6 +404,8 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
|
|||
%token<fl> yVLT_SC_BV "sc_bv"
|
||||
%token<fl> yVLT_SFORMAT "sformat"
|
||||
%token<fl> yVLT_SPLIT_VAR "split_var"
|
||||
%token<fl> yVLT_TIMING_OFF "timing_off"
|
||||
%token<fl> yVLT_TIMING_ON "timing_on"
|
||||
%token<fl> yVLT_TRACING_OFF "tracing_off"
|
||||
%token<fl> yVLT_TRACING_ON "tracing_on"
|
||||
|
||||
|
@ -1742,13 +1755,13 @@ net_dataTypeE<nodeDTypep>:
|
|||
// // Otherwise #(...) can't be determined to be a delay or parameters
|
||||
// // Submit this as a footnote to the committee
|
||||
var_data_type { $$ = $1; }
|
||||
| signingE rangeList delayE
|
||||
| signingE rangeList delay_controlE
|
||||
{ $$ = GRAMMARP->addRange(new AstBasicDType{$2->fileline(), LOGIC, $1},
|
||||
$2, true);
|
||||
GRAMMARP->setNetDelay($3); } // not implicit
|
||||
| signing
|
||||
{ $$ = new AstBasicDType{$<fl>1, LOGIC, $1}; } // not implicit
|
||||
| /*implicit*/ delayE
|
||||
| /*implicit*/ delay_controlE
|
||||
{ $$ = new AstBasicDType{CRELINE(), LOGIC};
|
||||
GRAMMARP->setNetDelay($1); } // not implicit
|
||||
;
|
||||
|
@ -2428,14 +2441,10 @@ module_common_item<nodep>: // ==IEEE: module_common_item
|
|||
;
|
||||
|
||||
continuous_assign<nodep>: // IEEE: continuous_assign
|
||||
yASSIGN strengthSpecE delayE assignList ';'
|
||||
yASSIGN strengthSpecE delay_controlE assignList ';'
|
||||
{
|
||||
$$ = $4;
|
||||
if ($3)
|
||||
for (auto* nodep = $$; nodep; nodep = nodep->nextp()) {
|
||||
auto* const assignp = VN_AS(nodep, NodeAssign);
|
||||
assignp->addTimingControlp(nodep == $$ ? $3 : $3->cloneTree(false));
|
||||
}
|
||||
PUT_DLYS_IN_ASSIGNS($3, $$);
|
||||
}
|
||||
;
|
||||
|
||||
|
@ -2678,27 +2687,22 @@ assignOne<nodep>:
|
|||
;
|
||||
|
||||
delay_or_event_controlE<nodep>: // IEEE: delay_or_event_control plus empty
|
||||
/* empty */ { $$ = nullptr; }
|
||||
| delay_control { $$ = $1; }
|
||||
| event_control { $$ = $1; }
|
||||
//UNSUP | yREPEAT '(' expr ')' event_control { }
|
||||
;
|
||||
|
||||
delay_controlE<nodep>:
|
||||
/* empty */ { $$ = nullptr; }
|
||||
| delay_control { $$ = $1; }
|
||||
| event_control { $$ = $1; }
|
||||
//UNSUP | yREPEAT '(' expr ')' event_control { }
|
||||
;
|
||||
|
||||
delayE<nodep>:
|
||||
/* empty */ { $$ = nullptr; }
|
||||
| delay { $$ = $1; }
|
||||
;
|
||||
|
||||
delay<nodep>:
|
||||
delay_control
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
delay_control<nodep>: //== IEEE: delay_control
|
||||
'#' delay_value { $$ = $2; }
|
||||
| '#' '(' minTypMax ')' { $$ = $3; }
|
||||
| '#' '(' minTypMax ',' minTypMax ')' { $$ = $3; DEL($5); }
|
||||
| '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' { $$ = $3; DEL($5); DEL($7); }
|
||||
| '#' '(' minTypMax ',' minTypMax ')' { $$ = $3; RISEFALLDLYUNSUP($3); DEL($5); }
|
||||
| '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' { $$ = $3; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); }
|
||||
;
|
||||
|
||||
delay_value<nodep>: // ==IEEE:delay_value
|
||||
|
@ -2715,7 +2719,7 @@ delayExpr<nodep>:
|
|||
|
||||
minTypMax<nodep>: // IEEE: mintypmax_expression and constant_mintypmax_expression
|
||||
delayExpr { $$ = $1; }
|
||||
| delayExpr ':' delayExpr ':' delayExpr { $$ = $3; DEL($1); DEL($5); }
|
||||
| delayExpr ':' delayExpr ':' delayExpr { $$ = $3; MINTYPMAXDLYUNSUP($3); DEL($1); DEL($5); }
|
||||
;
|
||||
|
||||
netSigList<varp>: // IEEE: list_of_port_identifiers
|
||||
|
@ -2729,8 +2733,9 @@ netSig<varp>: // IEEE: net_decl_assignment - one element from
|
|||
| netId sigAttrListE '=' expr
|
||||
{ $$ = VARDONEA($<fl>1, *$1, nullptr, $2);
|
||||
auto* const assignp = new AstAssignW{$3, new AstVarRef{$<fl>1, *$1, VAccess::WRITE}, $4};
|
||||
if ($$->delayp()) assignp->addTimingControlp($$->delayp()->unlinkFrBack()); // IEEE 1800-2017 10.3.3
|
||||
$$->addNext(assignp); } | netId variable_dimensionList sigAttrListE
|
||||
if ($$->delayp()) assignp->timingControlp($$->delayp()->unlinkFrBack()); // IEEE 1800-2017 10.3.3
|
||||
$$->addNext(assignp); }
|
||||
| netId variable_dimensionList sigAttrListE
|
||||
{ $$ = VARDONEA($<fl>1,*$1, $2, $3); }
|
||||
;
|
||||
|
||||
|
@ -3270,8 +3275,14 @@ statement_item<nodep>: // IEEE: statement_item
|
|||
//
|
||||
| par_block { $$ = $1; }
|
||||
// // IEEE: procedural_timing_control_statement + procedural_timing_control
|
||||
| delay_control stmtBlock { $$ = new AstDelay{$1->fileline(), $1, $2}; }
|
||||
| event_control stmtBlock { $$ = new AstEventControl(FILELINE_OR_CRE($1), $1, $2); }
|
||||
| delay_control stmtBlock { AstNode* nextp = nullptr;
|
||||
if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext();
|
||||
$$ = new AstDelay{$1->fileline(), $1, $2};
|
||||
$$->addNextNull(nextp); }
|
||||
| event_control stmtBlock { AstNode* nextp = nullptr;
|
||||
if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext();
|
||||
$$ = new AstEventControl{FILELINE_OR_CRE($1), $1, $2};
|
||||
$$->addNextNull(nextp); }
|
||||
//UNSUP cycle_delay stmtBlock { UNSUP }
|
||||
//
|
||||
| seq_block { $$ = $1; }
|
||||
|
@ -4704,33 +4715,33 @@ stream_expressionOrDataType<nodep>: // IEEE: from streaming_concatenation
|
|||
// Gate declarations
|
||||
|
||||
gateDecl<nodep>:
|
||||
yBUF delayE gateBufList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yBUFIF0 delayE gateBufif0List ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yBUFIF1 delayE gateBufif1List ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yNOT delayE gateNotList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yNOTIF0 delayE gateNotif0List ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yNOTIF1 delayE gateNotif1List ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yAND delayE gateAndList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yNAND delayE gateNandList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yOR delayE gateOrList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yNOR delayE gateNorList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yXOR delayE gateXorList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yXNOR delayE gateXnorList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yPULLUP delayE gatePullupList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yPULLDOWN delayE gatePulldownList ';' { $$ = $3; PRIMDLYUNSUP($2); }
|
||||
| yNMOS delayE gateBufif1List ';' { $$ = $3; PRIMDLYUNSUP($2); } // ~=bufif1, as don't have strengths yet
|
||||
| yPMOS delayE gateBufif0List ';' { $$ = $3; PRIMDLYUNSUP($2); } // ~=bufif0, as don't have strengths yet
|
||||
yBUF delay_controlE gateBufList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yBUFIF0 delay_controlE gateBufif0List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yBUFIF1 delay_controlE gateBufif1List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yNOT delay_controlE gateNotList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yNOTIF0 delay_controlE gateNotif0List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yNOTIF1 delay_controlE gateNotif1List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yAND delay_controlE gateAndList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yNAND delay_controlE gateNandList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yOR delay_controlE gateOrList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yNOR delay_controlE gateNorList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yXOR delay_controlE gateXorList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yXNOR delay_controlE gateXnorList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yPULLUP delay_controlE gatePullupList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yPULLDOWN delay_controlE gatePulldownList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); }
|
||||
| yNMOS delay_controlE gateBufif1List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } // ~=bufif1, as don't have strengths yet
|
||||
| yPMOS delay_controlE gateBufif0List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } // ~=bufif0, as don't have strengths yet
|
||||
//
|
||||
| yTRAN delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tran"); } // Unsupported
|
||||
| yRCMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rcmos"); } // Unsupported
|
||||
| yCMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"cmos"); } // Unsupported
|
||||
| yRNMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rmos"); } // Unsupported
|
||||
| yRPMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"pmos"); } // Unsupported
|
||||
| yRTRAN delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtran"); } // Unsupported
|
||||
| yRTRANIF0 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif0"); } // Unsupported
|
||||
| yRTRANIF1 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif1"); } // Unsupported
|
||||
| yTRANIF0 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif0"); } // Unsupported
|
||||
| yTRANIF1 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif1"); } // Unsupported
|
||||
| yTRAN delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tran"); } // Unsupported
|
||||
| yRCMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rcmos"); } // Unsupported
|
||||
| yCMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"cmos"); } // Unsupported
|
||||
| yRNMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rmos"); } // Unsupported
|
||||
| yRPMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"pmos"); } // Unsupported
|
||||
| yRTRAN delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtran"); } // Unsupported
|
||||
| yRTRANIF0 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif0"); } // Unsupported
|
||||
| yRTRANIF1 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif1"); } // Unsupported
|
||||
| yTRANIF0 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif0"); } // Unsupported
|
||||
| yTRANIF1 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif1"); } // Unsupported
|
||||
;
|
||||
|
||||
gateBufList<nodep>:
|
||||
|
@ -6541,6 +6552,7 @@ vltItem:
|
|||
|
||||
vltOffFront<errcodeen>:
|
||||
yVLT_COVERAGE_OFF { $$ = V3ErrorCode::I_COVERAGE; }
|
||||
| yVLT_TIMING_OFF { $$ = V3ErrorCode::I_TIMING; }
|
||||
| yVLT_TRACING_OFF { $$ = V3ErrorCode::I_TRACING; }
|
||||
| yVLT_LINT_OFF { $$ = V3ErrorCode::I_LINT; }
|
||||
| yVLT_LINT_OFF yVLT_D_RULE idAny
|
||||
|
@ -6550,6 +6562,7 @@ vltOffFront<errcodeen>:
|
|||
|
||||
vltOnFront<errcodeen>:
|
||||
yVLT_COVERAGE_ON { $$ = V3ErrorCode::I_COVERAGE; }
|
||||
| yVLT_TIMING_ON { $$ = V3ErrorCode::I_TIMING; }
|
||||
| yVLT_TRACING_ON { $$ = V3ErrorCode::I_TRACING; }
|
||||
| yVLT_LINT_ON { $$ = V3ErrorCode::I_LINT; }
|
||||
| yVLT_LINT_ON yVLT_D_RULE idAny
|
||||
|
|
|
@ -1125,7 +1125,7 @@ sub compile {
|
|||
}
|
||||
|
||||
if (!$param{fails} && $param{make_main}) {
|
||||
$self->_make_main();
|
||||
$self->_make_main($param{timing_loop});
|
||||
}
|
||||
|
||||
if ($param{verilator_make_gmake}
|
||||
|
@ -1480,6 +1480,12 @@ sub have_sc {
|
|||
return 0;
|
||||
}
|
||||
|
||||
sub have_coroutines {
|
||||
my $self = (ref $_[0] ? shift : $Self);
|
||||
return 1 if $self->verilator_version =~ /coroutine support *= *1/i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub make_version {
|
||||
my $ver = `$ENV{MAKE} --version`;
|
||||
if ($ver =~ /make ([0-9]+\.[0-9]+)/i) {
|
||||
|
@ -1731,6 +1737,11 @@ sub _try_regex {
|
|||
|
||||
sub _make_main {
|
||||
my $self = shift;
|
||||
my $timing_loop = shift;
|
||||
|
||||
if ($timing_loop && $self->sc) {
|
||||
$self->error("Cannot use timing loop and SystemC together!\n");
|
||||
}
|
||||
|
||||
if ($self->vhdl) {
|
||||
$self->_read_inputs_vhdl();
|
||||
|
@ -1859,33 +1870,61 @@ sub _make_main {
|
|||
}
|
||||
print $fh " ${set}fastclk = false;\n" if $self->{inputs}{fastclk};
|
||||
print $fh " ${set}clk = false;\n" if $self->{inputs}{clk};
|
||||
_print_advance_time($self, $fh, 10);
|
||||
if (!$timing_loop) {
|
||||
_print_advance_time($self, $fh, 10);
|
||||
}
|
||||
print $fh " }\n";
|
||||
|
||||
my $time = $self->sc ? "sc_time_stamp()" : "contextp->time()";
|
||||
|
||||
print $fh " while ((${time} < sim_time * MAIN_TIME_MULTIPLIER)\n";
|
||||
print $fh " && !contextp->gotFinish()) {\n";
|
||||
print $fh " while (";
|
||||
if (!$timing_loop || $self->{inputs}{clk}) {
|
||||
print $fh "(${time} < sim_time * MAIN_TIME_MULTIPLIER) && ";
|
||||
}
|
||||
print $fh "!contextp->gotFinish()) {\n";
|
||||
|
||||
for (my $i = 0; $i < 5; $i++) {
|
||||
my $action = 0;
|
||||
if ($self->{inputs}{fastclk}) {
|
||||
print $fh " ${set}fastclk = !${set}fastclk;\n";
|
||||
$action = 1;
|
||||
if ($timing_loop) {
|
||||
print $fh " topp->eval();\n";
|
||||
if ($self->{trace}) {
|
||||
$fh->print("#if VM_TRACE\n");
|
||||
$fh->print(" if (tfp) tfp->dump(contextp->time());\n");
|
||||
$fh->print("#endif // VM_TRACE\n");
|
||||
}
|
||||
if ($i == 0 && $self->{inputs}{clk}) {
|
||||
print $fh " ${set}clk = !${set}clk;\n";
|
||||
$action = 1;
|
||||
if ($self->{inputs}{clk}) {
|
||||
print $fh " uint64_t cycles = contextp->time() / MAIN_TIME_MULTIPLIER;\n";
|
||||
print $fh " uint64_t new_time = (cycles + 1) * MAIN_TIME_MULTIPLIER;\n";
|
||||
print $fh " if (topp->eventsPending() &&\n";
|
||||
print $fh " topp->nextTimeSlot() / MAIN_TIME_MULTIPLIER <= cycles) {\n";
|
||||
print $fh " new_time = topp->nextTimeSlot();\n";
|
||||
print $fh " } else {\n";
|
||||
print $fh " ${set}clk = !${set}clk;\n";
|
||||
print $fh " }\n";
|
||||
print $fh " contextp->time(new_time);\n";
|
||||
} else {
|
||||
print $fh " if (!topp->eventsPending()) break;\n";
|
||||
print $fh " contextp->time(topp->nextTimeSlot());\n";
|
||||
}
|
||||
if ($self->{savable}) {
|
||||
$fh->print(" if (save_time && ${time} == save_time) {\n");
|
||||
$fh->print(" save_model(\"$self->{obj_dir}/saved.vltsv\");\n");
|
||||
$fh->print(" printf(\"Exiting after save_model\\n\");\n");
|
||||
$fh->print(" topp.reset(nullptr);\n");
|
||||
$fh->print(" return 0;\n");
|
||||
$fh->print(" }\n");
|
||||
} else {
|
||||
for (my $i = 0; $i < 5; $i++) {
|
||||
my $action = 0;
|
||||
if ($self->{inputs}{fastclk}) {
|
||||
print $fh " ${set}fastclk = !${set}fastclk;\n";
|
||||
$action = 1;
|
||||
}
|
||||
if ($i == 0 && $self->{inputs}{clk}) {
|
||||
print $fh " ${set}clk = !${set}clk;\n";
|
||||
$action = 1;
|
||||
}
|
||||
if ($self->{savable}) {
|
||||
$fh->print(" if (save_time && ${time} == save_time) {\n");
|
||||
$fh->print(" save_model(\"$self->{obj_dir}/saved.vltsv\");\n");
|
||||
$fh->print(" printf(\"Exiting after save_model\\n\");\n");
|
||||
$fh->print(" topp.reset(nullptr);\n");
|
||||
$fh->print(" return 0;\n");
|
||||
$fh->print(" }\n");
|
||||
}
|
||||
_print_advance_time($self, $fh, 1, $action);
|
||||
}
|
||||
_print_advance_time($self, $fh, 1, $action);
|
||||
}
|
||||
if ($self->{benchmarksim}) {
|
||||
$fh->print(" if (VL_UNLIKELY(!warm)) {\n");
|
||||
|
@ -1941,7 +1980,7 @@ sub _print_advance_time {
|
|||
else { $set = "topp->"; }
|
||||
|
||||
if ($self->sc) {
|
||||
print $fh " sc_start(${time}, $Self->{sc_time_resolution});\n";
|
||||
print $fh " sc_start(${time} * MAIN_TIME_MULTIPLIER, $Self->{sc_time_resolution});\n";
|
||||
} else {
|
||||
if ($action) {
|
||||
print $fh " ${set}eval();\n";
|
||||
|
|
|
@ -14,7 +14,7 @@ top_filename("t/t_altera_lpm.v");
|
|||
(my $module = $Self->{name}) =~ s/.*t_altera_//;
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--top-module ${module}"]
|
||||
verilator_flags2 => ["--top-module ${module}", "--no-timing"]
|
||||
);
|
||||
|
||||
ok(1);
|
||||
|
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ['-Wno-STMTDLY -Wno-ASSIGNDLY'],
|
||||
verilator_flags2 => ['-Wno-STMTDLY -Wno-ASSIGNDLY --no-timing'],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
// any use, without warranty, 2003 by Wilson Snyder.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`timescale 100ns/1ns
|
||||
|
||||
module t (/*AUTOARG*/
|
||||
// Inputs
|
||||
clk
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
%Error: t/t_delay_func_bad.v:10:8: Delays are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4)
|
||||
: ... In instance t
|
||||
10 | #1 $stop;
|
||||
| ^
|
||||
%Error: t/t_delay_func_bad.v:23:8: Delays are not legal in final blocks (IEEE 1800-2017 9.2.3)
|
||||
: ... In instance t
|
||||
23 | #1;
|
||||
| ^
|
||||
%Error: Exiting due to
|
|
@ -1,27 +0,0 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2020 by Wilson Snyder.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t(/*AUTOARG*/);
|
||||
|
||||
function int f;
|
||||
#1 $stop;
|
||||
f = 0;
|
||||
endfunction
|
||||
|
||||
int i;
|
||||
|
||||
initial begin
|
||||
i = f();
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
|
||||
final begin
|
||||
#1;
|
||||
$stop;
|
||||
end
|
||||
|
||||
endmodule
|
|
@ -1,31 +1,31 @@
|
|||
%Warning-ASSIGNDLY: t/t_delay.v:22:13: Unsupported: Ignoring timing control on this assignment.
|
||||
%Warning-ASSIGNDLY: t/t_delay.v:24:13: Ignoring timing control on this assignment/primitive due to --no-timing
|
||||
: ... In instance t
|
||||
22 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1;
|
||||
24 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1;
|
||||
| ^~~~~~~~~~~~~~~~~~
|
||||
... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest
|
||||
... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message.
|
||||
%Warning-ASSIGNDLY: t/t_delay.v:27:19: Unsupported: Ignoring timing control on this assignment.
|
||||
%Warning-ASSIGNDLY: t/t_delay.v:29:19: Ignoring timing control on this assignment/primitive due to --no-timing
|
||||
: ... In instance t
|
||||
27 | dly0 <= #0 32'h11;
|
||||
29 | dly0 <= #0 32'h11;
|
||||
| ^
|
||||
%Warning-ASSIGNDLY: t/t_delay.v:30:19: Unsupported: Ignoring timing control on this assignment.
|
||||
%Warning-ASSIGNDLY: t/t_delay.v:32:19: Ignoring timing control on this assignment/primitive due to --no-timing
|
||||
: ... In instance t
|
||||
30 | dly0 <= #0.12 dly0 + 32'h12;
|
||||
32 | dly0 <= #0.12 dly0 + 32'h12;
|
||||
| ^~~~
|
||||
%Warning-ASSIGNDLY: t/t_delay.v:38:26: Unsupported: Ignoring timing control on this assignment.
|
||||
%Warning-ASSIGNDLY: t/t_delay.v:40:26: Ignoring timing control on this assignment/primitive due to --no-timing
|
||||
: ... In instance t
|
||||
38 | dly0 <= #(dly_s.dly) 32'h55;
|
||||
40 | dly0 <= #(dly_s.dly) 32'h55;
|
||||
| ^~~
|
||||
%Warning-STMTDLY: t/t_delay.v:43:11: Unsupported: Ignoring delay on this delayed statement.
|
||||
%Warning-STMTDLY: t/t_delay.v:45:11: Ignoring delay on this statement due to --no-timing
|
||||
: ... In instance t
|
||||
43 | #100 $finish;
|
||||
45 | #100 $finish;
|
||||
| ^~~
|
||||
%Warning-UNUSED: t/t_delay.v:20:12: Signal is not used: 'dly_s'
|
||||
%Warning-UNUSED: t/t_delay.v:22:12: Signal is not used: 'dly_s'
|
||||
: ... In instance t
|
||||
20 | dly_s_t dly_s;
|
||||
22 | dly_s_t dly_s;
|
||||
| ^~~~~
|
||||
%Warning-BLKSEQ: t/t_delay.v:37:20: Blocking assignment '=' in sequential logic process
|
||||
%Warning-BLKSEQ: t/t_delay.v:39:20: Blocking assignment '=' in sequential logic process
|
||||
: ... Suggest using delayed assignment '<='
|
||||
37 | dly_s.dly = 55;
|
||||
39 | dly_s.dly = 55;
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
|
|
|
@ -13,7 +13,7 @@ scenarios(vlt => 1);
|
|||
top_filename("t/t_delay.v");
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ['-Wall -Wno-DECLFILENAME'],
|
||||
verilator_flags2 => ['--no-timing -Wall -Wno-DECLFILENAME'],
|
||||
fails => 1,
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Antmicro Ltd. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
$Self->{main_time_multiplier} = 10e-7 / 10e-9;
|
||||
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
top_filename("t/t_delay.v");
|
||||
|
||||
compile(
|
||||
timing_loop => 1,
|
||||
verilator_flags2 => ['--timing -Wno-ZERODLY'],
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
|
@ -0,0 +1,12 @@
|
|||
%Error-NOTIMING: t/t_event_control.v:14:7: Event control statement in this location requires --timing
|
||||
: ... In instance t
|
||||
: ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure
|
||||
14 | @(clk);
|
||||
| ^
|
||||
... For error description see https://verilator.org/warn/NOTIMING?v=latest
|
||||
%Error-NOTIMING: t/t_event_control.v:16:7: Event control statement in this location requires --timing
|
||||
: ... In instance t
|
||||
: ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure
|
||||
16 | @(clk);
|
||||
| ^
|
||||
%Error: Exiting due to
|
|
@ -11,13 +11,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ['--no-timing'],
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
|
||||
ok(1);
|
||||
1;
|
|
@ -0,0 +1,3 @@
|
|||
[10] Got
|
||||
[15] Got
|
||||
*-* All Finished *-*
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Antmicro Ltd. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
top_filename("t/t_event_control.v");
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--timing"],
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
|
@ -1,12 +0,0 @@
|
|||
%Error-UNSUPPORTED: t/t_event_control_unsup.v:14:7: Unsupported: event control statement in this location
|
||||
: ... In instance t
|
||||
: ... Suggest have one event control statement per procedure, at the top of the procedure
|
||||
14 | @(clk);
|
||||
| ^
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_event_control_unsup.v:16:7: Unsupported: event control statement in this location
|
||||
: ... In instance t
|
||||
: ... Suggest have one event control statement per procedure, at the top of the procedure
|
||||
16 | @(clk);
|
||||
| ^
|
||||
%Error: Exiting due to
|
|
@ -1,6 +1,6 @@
|
|||
%Error-UNSUPPORTED: t/t_fork.v:10:14: Unsupported: fork statements
|
||||
: ... In instance t
|
||||
%Error-NOTIMING: t/t_fork.v:10:14: Fork statements require --timing
|
||||
: ... In instance t
|
||||
10 | fork : fblk
|
||||
| ^~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
... For error description see https://verilator.org/warn/NOTIMING?v=latest
|
||||
%Error: Exiting due to
|
||||
|
|
|
@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(vlt => 1);
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ['--no-timing'],
|
||||
fails => 1,
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(vlt => 1);
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ['--lint-only --bbox-unsup'],
|
||||
verilator_flags2 => ['--lint-only --no-timing --bbox-unsup'],
|
||||
);
|
||||
|
||||
ok(1);
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
%Error-UNSUPPORTED: t/t_fork_disable.v:12:7: Unsupported: fork statements
|
||||
: ... In instance t
|
||||
12 | fork
|
||||
| ^~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_fork_disable.v:16:7: Unsupported: disable fork statements
|
||||
: ... In instance t
|
||||
16 | disable fork;
|
||||
| ^~~~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_fork_disable.v:17:7: Unsupported: wait fork statements
|
||||
: ... In instance t
|
||||
17 | wait fork;
|
||||
|
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(linter => 1);
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ['--lint-only'],
|
||||
verilator_flags2 => ['--lint-only --timing'],
|
||||
fails => 1,
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
|
|
@ -10,7 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile();
|
||||
compile(
|
||||
verilator_flags2 => ['--no-timing'],
|
||||
);
|
||||
|
||||
execute();
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Antmicro Ltd. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
top_filename("t/t_fork_label.v");
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--exe --main --timing"],
|
||||
make_main => 0,
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Antmicro Ltd. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(vlt => 1);
|
||||
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
top_filename("t/t_fork.v");
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--timing"],
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
|
@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(vlt_all => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ['--no-timing'],
|
||||
);
|
||||
# No execute
|
||||
ok(1);
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Antmicro Ltd. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(vlt => 1); # UNOPTTHREADS in vltmt
|
||||
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
top_filename("t/t_func_lib_sub.v");
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--timing"],
|
||||
);
|
||||
}
|
||||
# No execute
|
||||
ok(1);
|
||||
1;
|
|
@ -13,7 +13,7 @@ scenarios(vlt_all => 1);
|
|||
compile(
|
||||
make_top_shell => 0,
|
||||
make_main => 0,
|
||||
verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp"],
|
||||
verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp", "--no-timing"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--no-timing"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2022 by Antmicro Ltd. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
$Self->{main_time_multiplier} = 10e-7 / 10e-9;
|
||||
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
top_filename("t/t_gate_basic.v");
|
||||
|
||||
compile(
|
||||
timing_loop => 1,
|
||||
verilator_flags2 => ["--timing --timescale 10ns/1ns -Wno-RISEFALLDLY"],
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
|
@ -1,12 +1,6 @@
|
|||
%Warning-ASSIGNDLY: t/t_gate_basic.v:23:12: Unsupported: Ignoring delay on this primitive.
|
||||
23 | not #(0.108) NT0 (nt0, a[0]);
|
||||
| ^~~~~
|
||||
... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest
|
||||
... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message.
|
||||
%Warning-ASSIGNDLY: t/t_gate_basic.v:24:11: Unsupported: Ignoring delay on this primitive.
|
||||
24 | and #1 AN0 (an0, a[0], b[0]);
|
||||
| ^
|
||||
%Warning-ASSIGNDLY: t/t_gate_basic.v:25:12: Unsupported: Ignoring delay on this primitive.
|
||||
%Warning-RISEFALLDLY: t/t_gate_basic.v:25:12: Unsupported: rising/falling/turn-off delays. Using the first delay
|
||||
25 | nand #(2,3) ND0 (nd0, a[0], b[0], b[1]);
|
||||
| ^
|
||||
... For warning description see https://verilator.org/warn/RISEFALLDLY?v=latest
|
||||
... Use "/* verilator lint_off RISEFALLDLY */" and lint_on around source to disable this message.
|
||||
%Error: Exiting due to
|
||||
|
|
|
@ -13,7 +13,7 @@ scenarios(linter => 1);
|
|||
top_filename("t/t_gate_basic.v");
|
||||
|
||||
lint(
|
||||
verilator_flags2 => ["--lint-only -Wall -Wno-DECLFILENAME -Wno-UNUSED"],
|
||||
verilator_flags2 => ["--lint-only -Wall -Wno-DECLFILENAME -Wno-UNUSED --timing"],
|
||||
fails => 1,
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ scenarios(simulator => 1);
|
|||
|
||||
compile(
|
||||
nc_flags2 => ['+access+r'],
|
||||
verilator_flags2 => ["--no-timing"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
|||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--no-timing"],
|
||||
);
|
||||
|
||||
execute(
|
||||
|
|
|
@ -30,6 +30,7 @@ while (1) {
|
|||
run(logfile => "$secret_dir/vlt_compile.log",
|
||||
cmd => ["perl",
|
||||
"$ENV{VERILATOR_ROOT}/bin/verilator",
|
||||
'--no-timing',
|
||||
"--prefix",
|
||||
"Vt_lib_prot_secret",
|
||||
"-cc",
|
||||
|
@ -52,6 +53,7 @@ while (1) {
|
|||
|
||||
compile(
|
||||
verilator_flags2 => ["$secret_dir/secret.sv",
|
||||
'--no-timing',
|
||||
"-LDFLAGS",
|
||||
"$secret_prefix/libsecret.a"],
|
||||
xsim_flags2 => ["$secret_dir/secret.sv"],
|
||||
|
|
|
@ -21,7 +21,7 @@ top_filename("t/t_lib_prot.v");
|
|||
|
||||
# Tests the same code as t_lib_prot.pl but without --protect-lib
|
||||
compile(
|
||||
verilator_flags2 => ["t/t_lib_prot_secret.v"],
|
||||
verilator_flags2 => ["t/t_lib_prot_secret.v", '--no-timing'],
|
||||
xsim_flags2 => ["t/t_lib_prot_secret.v"],
|
||||
);
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ while (1) {
|
|||
run(logfile => "$secret_dir/vlt_compile.log",
|
||||
cmd => ["perl",
|
||||
"$ENV{VERILATOR_ROOT}/bin/verilator",
|
||||
'--no-timing',
|
||||
"--prefix",
|
||||
"Vt_lib_prot_secret",
|
||||
"-cc",
|
||||
|
@ -52,6 +53,7 @@ while (1) {
|
|||
|
||||
compile(
|
||||
verilator_flags2 => ["$secret_dir/secret.sv",
|
||||
'--no-timing',
|
||||
"-LDFLAGS",
|
||||
"$secret_prefix/libsecret.a"],
|
||||
xsim_flags2 => ["$secret_dir/secret.sv"],
|
||||
|
|
|
@ -29,6 +29,7 @@ while (1) {
|
|||
run(logfile => "$secret_dir/vlt_compile.log",
|
||||
cmd => ["perl",
|
||||
"$ENV{VERILATOR_ROOT}/bin/verilator",
|
||||
'--no-timing',
|
||||
"--prefix",
|
||||
"Vt_lib_prot_secret",
|
||||
"-cc",
|
||||
|
@ -54,6 +55,7 @@ while (1) {
|
|||
|
||||
compile(
|
||||
verilator_flags2 => ["$secret_dir/secret.sv",
|
||||
'--no-timing',
|
||||
"-GGATED_CLK=1",
|
||||
"-LDFLAGS",
|
||||
"$secret_prefix/libsecret.a"],
|
||||
|
|
|
@ -32,6 +32,7 @@ while (1) {
|
|||
cmd => ["perl",
|
||||
"$ENV{VERILATOR_ROOT}/bin/verilator",
|
||||
($Self->{vltmt} ? ' --threads 6' : ''),
|
||||
'--no-timing',
|
||||
"--prefix",
|
||||
"Vt_lib_prot_secret",
|
||||
"-cc",
|
||||
|
@ -56,6 +57,7 @@ while (1) {
|
|||
|
||||
compile(
|
||||
verilator_flags2 => ["$secret_dir/secret.sv",
|
||||
'--no-timing',
|
||||
"-LDFLAGS",
|
||||
"'-Wl,-rpath,$abs_secret_dir -L$abs_secret_dir -l$secret_prefix'"],
|
||||
xsim_flags2 => ["$secret_dir/secret.sv"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:8: Unsupported: Ignoring delay on this delayed statement.
|
||||
%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:8: Ignoring delay on this statement due to --no-timing
|
||||
: ... In instance t
|
||||
10 | #100 $finish;
|
||||
| ^~~
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue