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:
Krzysztof Bieganski 2022-08-22 14:26:32 +02:00 committed by GitHub
parent 7cc89b8b42
commit 39af5d020e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
201 changed files with 8216 additions and 538 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -88,6 +88,7 @@ Nathan Kohagen
Nathan Myers
Patrick Stewart
Paul Wright
Pawel Sagan
Peter Horvath
Peter Monsson
Philipp Wagner

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

354
include/verilated_timing.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -280,6 +280,7 @@ private:
} else { // Track like any other statement
iterateAndNextNull(nodep->lhsp());
}
iterateNull(nodep->timingControlp());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

361
src/V3SchedTiming.cpp Normal file
View File

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

View File

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

659
src/V3Timing.cpp Normal file
View File

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

32
src/V3Timing.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
[10] Got
[15] Got
*-* All Finished *-*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

29
test_regress/t/t_fork_timing.pl Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ scenarios(simulator => 1);
compile(
nc_flags2 => ['+access+r'],
verilator_flags2 => ["--no-timing"],
);
execute(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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