IEEE compliant scheduler (#3384)

This is a major re-design of the way code is scheduled in Verilator,
with the goal of properly supporting the Active and NBA regions of the
SystemVerilog scheduling model, as defined in IEEE 1800-2017 chapter 4.

With this change, all internally generated clocks should simulate
correctly, and there should be no more need for the `clock_enable` and
`clocker` attributes for correctness in the absence of Verilator
generated library models (`--lib-create`).

Details of the new scheduling model and algorithm are provided in
docs/internals.rst.

Implements #3278
This commit is contained in:
Geza Lore 2022-05-15 16:03:32 +01:00 committed by GitHub
parent 39b7c47e7b
commit 599d23697d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
157 changed files with 6386 additions and 4395 deletions

View File

@ -14,6 +14,9 @@ Verilator 5.001 devel
**Major:**
* This is a major new release, currently only in alpha testing.
* 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]
Verilator 4.223 devel

View File

@ -355,7 +355,6 @@ detailed descriptions of these arguments.
-O3 High performance optimizations
-O<optimization-letter> Selectable optimizations
-o <executable> Name of final executable
--no-order-clock-delay Disable ordering clock enable assignments
--no-verilate Skip verilation and just compile previously Verilated code.
--output-split <statements> Split .cpp files into pieces
--output-split-cfuncs <statements> Split model functions

View File

@ -1,7 +1,5 @@
.. comment: generated by t_lint_didnotconverge_bad
.. code-block::
-V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request
-V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request_1
-V{t#,#} CHANGE: t/t_lint_didnotconverge_bad.v:14: a
%Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b)
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge.

View File

@ -1,4 +1,4 @@
.. comment: generated by t_lint_didnotconverge_nodbg_bad
.. code-block::
%Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge.

View File

@ -158,10 +158,7 @@ Summary:
.. option:: --clk <signal-name>
With :vlopt:`--clk`, the specified signal-name is taken as a root clock
into the model; Verilator will mark the signal as clocker and
propagate the clocker attribute automatically to other signals downstream in
that clock tree.
With :vlopt:`--clk`, the specified signal is marked as a clock signal.
The provided signal-name is specified using a RTL hierarchy path. For
example, v.foo.bar. If the signal is the input to top-module, then
@ -173,11 +170,11 @@ Summary:
individual bits, Verilator will attempt to decompose the vector and
connect the single-bit clock signals.
The clocker attribute is useful in cases where Verilator does not
properly distinguish clock signals from other data signals. Using
clocker will cause the signal indicated to be considered a clock, and
remove it from the combinatorial logic reevaluation checking code. This
may greatly improve performance.
In versions prior to 5.002, the clocker attribute is useful in cases where
Verilator does not properly distinguish clock signals from other data
signals. Using clocker will cause the signal indicated to be considered a
clock, and remove it from the combinatorial logic reevaluation checking
code. This may greatly improve performance.
.. option:: --compiler <compiler-name>
@ -714,6 +711,10 @@ Summary:
.. option:: --no-order-clock-delay
Deprecated and has no effect (ignored).
In versions prior to 5.002:
Rarely needed. Disables a bug fix for ordering of clock enables with
delayed assignments. This option should only be used when suggested by
the developers.
@ -1255,8 +1256,7 @@ Summary:
Enable all code style warnings, including code style warnings that are
normally disabled by default. Equivalent to :vlopt:`-Wwarn-lint`
:vlopt:`-Wwarn-style`. Excludes some specialty warnings,
i.e. IMPERFECTSCH.
:vlopt:`-Wwarn-style`. Excludes some specialty warnings.
.. option:: -Werror-<message>
@ -1509,6 +1509,10 @@ The grammar of configuration commands is as follows:
.. option:: clock_enable -module "<modulename>" -var "<signame>"
Deprecated and has no effect (ignored).
In versions prior to 5.002:
Indicate the signal is used to gate a clock, and the user takes
responsibility for insuring there are no races related to it.

View File

@ -160,6 +160,10 @@ or "`ifdef`"'s may break other tools.
.. option:: /*verilator&32;clock_enable*/
Deprecated and has no effect (ignored).
In versions prior to 5.002:
Used after a signal declaration to indicate the signal is used to gate a
clock, and the user takes responsibility for insuring there are no races
related to it. (Typically by adding a latch, and running static timing
@ -184,9 +188,7 @@ or "`ifdef`"'s may break other tools.
.. option:: /*verilator&32;no_clocker*/
Specifies that the signal is used as clock or not. This information is
used by Verilator to mark the signal and any derived signals as
clocker. See :vlopt:`--clk`.
Specifies that the signal is used as clock or not. See :vlopt:`--clk`.
Same as :option:`clocker` and :option:`no_clocker` in configuration
files.

View File

@ -276,17 +276,6 @@ probably expect, what C does. The default behavior of Verilog is
different.)
Generated Clocks
----------------
Verilator attempts to deal with generated and gated clocks correctly,
however some cases cause problems in the scheduling algorithm which is
optimized for performance. The safest option is to have all clocks as
primary inputs to the model, or wires directly attached to primary inputs.
For proper behavior clock enables may also need the
:option:`/*verilator&32;clock_enable*/` metacomment.
Gate Primitives
---------------

View File

@ -5,6 +5,7 @@
Errors and Warnings
*******************
.. _Disabling Warnings:
Disabling Warnings
==================
@ -313,20 +314,6 @@ List Of Warnings
potential for reset glitches.
.. option:: CLKDATA
.. TODO better example
Warns that clock signal is mixed used with/as data signal. The checking
for this warning is enabled only if user has explicitly marked some
signal as clocker using command line option or in-source meta comment
(see :vlopt:`--clk`).
The warning can be disabled without affecting the simulation result. But
it is recommended to check the warning as this may degrade the
performance of the Verilated model.
.. option:: CMPCONST
.. TODO better example
@ -492,7 +479,7 @@ List Of Warnings
passing. Thus to prevent an infinite loop, the Verilated executable
gives the DIDNOTCONVERGE error.
To debug this, first review any UNOPT or UNOPTFLAT warnings that were
To debug this, first review any UNOPTFLAT warnings that were
ignored. Though typically it is safe to ignore UNOPTFLAT (at a
performance cost), at the time of issuing a UNOPTFLAT Verilator did not
know if the logic would eventually converge and assumed it would.
@ -587,13 +574,6 @@ List Of Warnings
with a newline."
.. option:: GENCLK
Deprecated and no longer used as a warning. Used to indicate that the
specified signal was is generated inside the model, and also being used
as a clock.
.. option:: HIERBLOCK
Warns that the top module is marked as a hierarchy block by the
@ -651,16 +631,6 @@ List Of Warnings
simulate correctly.
.. option:: IMPERFECTSCH
.. TODO better example
Warns that the scheduling of the model is not absolutely perfect, and
some manual code edits may result in faster performance. This warning
defaults to off, is not part of -Wall, and must be turned on explicitly
before the top module statement is processed.
.. option:: IMPLICIT
.. TODO better example
@ -1353,27 +1323,6 @@ List Of Warnings
undriven (...) and will be removed".
.. option:: UNOPT
.. TODO better example
Warns that due to some construct, optimization of the specified signal
or block is disabled. The construct should be cleaned up to improve
simulation performance.
A less obvious case of this is when a module instantiates two
submodules. Inside submodule A, signal I is input and signal O is
output. Likewise in submodule B, signal O is an input and I is an
output. A loop exists and a UNOPT warning will result if AI & AO both
come from and go to combinatorial blocks in both submodules, even if
they are unrelated always blocks. This affects performance because
Verilator would have to evaluate each submodule multiple times to
stabilize the signals crossing between the modules.
Ignoring this warning will only slow simulations, it will simulate
correctly.
.. option:: UNOPTFLAT
.. TODO better example
@ -1385,10 +1334,6 @@ List Of Warnings
performance; two times better performance may be possible by fixing
these warnings.
Unlike the ``UNOPT`` warning, this occurs after flattening the netlist,
and indicates a more basic problem, as the less obvious case described
under ``UNOPT`` does not apply.
Often UNOPTFLAT is caused by logic that isn't truly circular as viewed by
synthesis which analyzes interconnection per-bit, but is circular to
simulation which analyzes per-bus.
@ -1441,10 +1386,6 @@ List Of Warnings
the conflict. If you run with `--report-unoptflat` Verilator will
suggest possible candidates for :option:`/*verilator&32;split_var*/`.
The UNOPTFLAT warning may also be due to clock enables, identified from
the reported path going through a clock gating instance. To fix these,
use the clock_enable meta comment described above.
The UNOPTFLAT warning may also occur where outputs from a block of logic
are independent, but occur in the same always block. To fix this, use
the :option:`/*verilator&32;isolate_assignments*/` metacomment described
@ -1702,3 +1643,32 @@ List Of Warnings
The correct fix is to either size the 1 (:code:`32'h1`), or add the
width to the parameter definition (:code:`parameter [31:0]`), or add the
width to the parameter usage (:code:`{PAR[31:0], PAR[31:0]}`).
Historical Warnings
===================
The following list of warnings used to be issued by some earlier versions of
Verilator. The current version never issues these warnings. For compatibility,
these warning codes are still accepted by the message control mechanisms (see
:ref:`Disabling Warnings`), but have no other effect.
.. option:: CLKDATA
Historical, never issued by current version of Verilator.
.. option:: GENCLK
Historical, never issued by current version of Verilator.
.. option:: IMPERFECTSCH
Historical, never issued by current version of Verilator.
.. option:: UNOPT
Historical, never issued by current version of Verilator.

View File

@ -183,6 +183,327 @@ A number of predefined derived algorithm classes and access methods are
provided and documented in ``V3GraphAlg.cpp``.
Scheduling
----------
Verilator implements the Active and NBA regions of the SystemVerilog scheduling
model as described in IEEE 1800-2017 chapter 4, and in particular sections
4.5 and Figure 4.1. The static (verilation time) scheduling of SystemVerilog
processes is performed by code in the ``V3Sched`` namespace. The single
entry-point to the scheduling algorithm is ``V3Sched::schedule``. Some
preparatory transformations important for scheduling are also performed in
``V3Active`` and ``V3ActiveTop``. High level evaluation functions are
constructed by ``V3Order``, which ``V3Sched`` invokes on subsets of the logic
in the design.
Scheduling deals with the problem of evaluating 'logic' in the correct order
and the correct number of times in order to compute the correct state of the
SystemVerilog program. Throughout this section, we use the term 'logic' to
refer to all SystemVerilog constructs that describe the evolution of the state
of the program. In particular, all SystemVerilog processes and continuous
assignments are considered 'logic', but not for example variable definitions
without initialization or other miscellaneous constructs.
Classes of logic
^^^^^^^^^^^^^^^^
The first step in the scheduling algorithm is to gather all the logic present
in the design, and classify it based on the conditions under which the logic
needs to be evaluated.
The classes of logic we distinguish between are:
- SystemVerilog ``initial`` processes, that need to be executed once at
startup.
- Static variable initializers. These are a separate class as they need to be
executed before ``initial`` processes.
- SystemVerilog ``final`` processes.
- Combinational logic. Any process or construct that has an implicit
sensitivity list with no explicit sensitivities is considered 'combinational'
logic. This includes among other things, ``always @*`` and ``always_comb``
processes, and continuous assignments. Verilator also converts some other
``always`` processes to combinational logic in ``V3Active`` as described
below.
- Clocked logic. Any process or construct that has an explicit sensitivity
list, with no implicit sensitivities is considered 'clocked' (or
'sequential') logic. This includes among other things ``always`` and
``always_ff`` processes with an explicit sensitivity list.
Note that the distinction between clocked logic and combinational logic is only
important for the scheduling algorithm within Verilator as we handle the two
classes differently. It is possible to convert clocked logic into combinational
logic if the explicit sensitivity list of the clocked logic is the same as the
implicit sensitivity list of the equivalent combinational logic would be. The
canonical examples are: ``always @(a) x = a;``, which is considered to be
clocked logic by Verilator, and the equivalent ``assign x = a;``, which is
considered to be combinational logic. ``V3Active`` in fact converts all clocked
logic to combinational logic whenever possible, as this provides advantages for
scheduling as described below.
There is also a 'hybrid' logic class, which has both explicit and implicit
sensitivities. This kind of logic does not arise from a SystemVerilog
construct, but is created during scheduling to break combinational cycles.
Details of this process and the hybrid logic class are described below.
Scheduling of simple classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SystemVerilog ``initial`` and ``final`` blocks can be scheduled (executed) in an
arbitrary order.
Static variable initializers need to be executed in source code order in case
there is a dependency between initializers, but the ordering of static variable
initialization is otherwise not defined by the SystemVerilog standard
(particularly, in the presence of hierarchical references in static variable
initializers).
The scheduling algorithm handles all three of these classes the same way and
schedules the logic in these classes in source code order. This step yields the
``_eval_static``, ``_eval_initial`` and ``_eval_final`` functions which execute
the corresponding logic constructs.
Scheduling of clocked and combinational logic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For performance, clocked and combinational logic needs to be ordered.
Conceptually this minimizes the iterations through the evaluation loop
presented in the reference algorithm in the SystemVerilog standard (IEEE
1800-2017 section 4.5), by evaluating logic constructs in data-flow order.
Without going into a lot of detail here, accept that well thought out ordering
is crucial to good simulation performance, and also enables further
optimizations later on.
At the highest level, ordering is performed by ``V3Order::order``, which is
invoked by ``V3Sched::schedule`` on various subsets of the combinational and
clocked logic as described below. The important thing to highlight now is that
``V3Order::order`` operates by assuming that the state of all variables driven
by combinational logic are consistent with that combinational logic. While this
might seem subtle, it is very important, so here is an example:
::
always_comb d = q + 2;
always @(posedge clock) q <= d;
During ordering, ``V3Order`` will assume that ``d`` equals ``q + 2`` at the
beginning of an evaluation step. As a result it will order the clocked logic
first, and all downstream combinational logic (like the assignment to ``d``)
will execute after the clocked logic that drives inputs to the combinational
logic, in data-flow (or dependency) order. At the end of the evaluation step,
this ordering restores the invariant that variables driven by combinational
logic are consistent with that combinational logic (i.e.: the circuit is in a
settled/steady state).
One of the most important optimizations for performance is to only evaluate
combinational logic, if its inputs might have changed. For example, there is no
point in evaluating the above assignment to ``d`` on a negative edge of the
clock signal. Verilator does this by pushing the combinational logic into the
same (possibly multiple) event domains as the logic driving the inputs to that
combinational logic, and only evaluating the combinational logic if at least
one driving domains have been triggered. The impact of this activity gating is
very high (observed 100x slowdown on large designs when turning it off), it is
the reason we prefer to convert clocked logic to combinational logic in
``V3Active`` whenever possible.
The ordering procedure described above works straight forward unless there are
combinational logic constructs that are circularly dependent (a.k.a.: the
UNOPTFLAT warning). Combinational scheduling loops can arise in sound
(realizable) circuits as Verilator considers each SystemVerilog process as a
unit of scheduling (albeit we do try to split processes into smaller ones to
avoid this circularity problem whenever possible, this is not always possible).
Breaking combinational loops
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Combinational loops are broken by the introduction of instances of the 'hybrid'
logic class. As described in the previous section, combinational loops require
iteration until the logic is settled, in order to restore the invariant that
combinationally driven signals are consistent with the combinational logic.
To achieve this, ``V3Sched::schedule`` calls ``V3Sched::breakCycles``, which
builds a dependency graph of all combinational logic in the design, and then
breaks all combinational cycles by converting all combinational logic that
consumes a variable driven via a 'back-edge' into hybrid logic. Here
'back-edge' just means a graph edge that points from a higher rank vertex to a
lower rank vertex in some consistent ranking of the directed graph. Variables
driven via a back-edge in the dependency graph are marked, and all
combinational logic that depends on such variables is converted into hybrid
logic, with the back-edge driven variables listed as explicit 'changed'
sensitivities.
Hybrid logic is handled by ``V3Order`` mostly in the same way as combinational
logic, with two exceptions:
- Explicit sensitivities of hybrid logic are ignored for the purposes of
data-flow ordering with respect to other combinational or hybrid logic. I.e.:
an explicit sensitivity suppresses the implicit sensitivity on the same
variable. This cold also be interpreted as ordering the hybrid logic as if
all variables listed as explicit sensitivities were substituted as constants
with their current values.
- The explicit sensitivities are included as an additional driving domain of
the logic, and also cause evaluation when triggered.
This means that hybrid logic is evaluated when either any of its implicit
sensitivities might have been updated (the same way as combinational logic, by
pushing it into the domains that write those variables), or if any of its
explicit sensitivities are triggered.
The effect of this transformation is that ``V3Order`` can proceed as if there
are no combinational cycles (or alternatively, under the assumption that the
back-edge driven variables don't change during one evaluation pass). The
evaluation loop invoking the ordered code, will then re-invoke it on a follow
on iteration, if any of the explicit sensitivities of hybrid logic have
actually changed due to the previous invocation, iterating until all the
combinational (including hybrid) logic have settled.
One might wonder if there can be a race condition between clocked logic
triggered due to a combinational signal change from the previous evaluation
pass, and a combinational loop settling due to hybrid logic, if the clocked
logic reads the not yet settled combinationally driven signal. Such a race is
indeed possible, but our evaluation is consistent with the SystemVerilog
scheduling semantics (IEEE 1800-2017 chapter 4), and therefore any program that
exhibits such a race has non-deterministic behaviour according to the
SystemVerilog semantics, so we accept this.
Settling combinational logic after initialization
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
At the beginning of simulation, once static initializer and ``initial`` blocks
have been executed, we need to evaluate all combinational logic, in order to
restore the invariant utilized by ``V3Order`` that the state of all
combinationally driven variables are consistent with the combinational logic.
To achieve this, we invoke ``V3Order::order`` on all of the combinational and
hybrid logic, and iterate the resulting evaluation function until no more
hybrid logic is triggered. This yields the `_eval_settle` function which is
invoked at the beginning of simulation, after the `_eval_initial`.
Partitioning logic for correct NBA updates
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``V3Order`` can order logic corresponding to non-blocking assignments (NBAs) to
yield correct simulation results, as long as all the sensitivity expressions of
clocked logic triggered in the Active scheduling region of the current time
step are known up front. I.e.: the ordering of NBA updates is only correct if
derived clocks that are computed in an Active region update (that is, via a
blocking or continuous assignment) are known up front.
We can ensure this by partitioning the logic into two regions. Note these
regions are a concept of the Verilator scheduling algorithm and they do not
directly correspond to the similarly named SystemVerilog scheduling regions
as defined in the standard:
- All logic (clocked, combinational and hybrid) that transitively feeds into,
or drives, via a non-blocking or continuous assignments (or via any update
that SystemVerilog executes in the Active scheduling region), a variable that
is used in the explicit sensitivity list of some clocked or hybrid logic, is
assigned to the 'act' region.
- All other logic is assigned to the 'nba' region.
For completeness, note that a subset of the 'act' region logic, specifically,
the logic related to the pre-assignments of NBA updates (i.e.: AstAssignPre
nodes), is handled separately, but is executed as part of the 'act' region.
Also note that all logic representing the committing of an NBA (i.e.: Ast*Post)
nodes) will be in the 'nba' region. This means that the evaluation of the 'act'
region logic will not commit any NBA updates. As a result, the 'act' region
logic can be iterated to compute all derived clock signals up front.
The correspondence between the SystemVerilog Active and NBA scheduling regions,
and the internal 'act' and 'nba' regions, is that 'act' contains all Active
region logic that can compute a clock signal, while 'nba' contains all other
Active and NBA region logic. For example, if the only clocks in the design are
top level inputs, then 'act' will be empty, and 'nba' will contain the whole of
the design.
The partitioning described above is performed by ``V3Sched::partition``.
Replication of combinational logic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We will separately invoke ``V3Order::order`` on the 'act' and 'nba' region
logic.
Combinational logic that reads variables driven from both 'act' and 'nba'
region logic has the problem of needing to be re-evaluated even if only one of
the regions updates an input variable. We could pass additional trigger
expressions between the regions to make sure combinational logic is always
re-evaluated, or we can replicate combinational logic that is driven from
multiple regions, by copying it into each region that drives it. Experiments
show this simple replication works well performance-wise (and notably
``V3Combine`` is good at combining the replicated code), so this is what we do
in ``V3Sched::replicateLogic``.
In ``V3Sched::replicateLogic``, in addition to replicating logic into the 'act'
and 'nba' regions, we also replicate combinational (and hybrid) logic that
depends on top level inputs. These become a separate 'ico' region (Input
Combinational logic), which we will always evaluate at the beginning of a
time-step to ensure the combinational invariant holds even if input signals
have changed. Note that this eliminates the need of changing data and clock
signals on separate evaluations, as was necessary with earlier versions of
Verilator).
Constructing the top level `_eval` function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To construct the top level `_eval` function, which updates the state of the
circuit to the end of the current time step, we invoke ``V3Order::order``
separately on the 'ico', 'act' and 'nba' logic, which yields the `_eval_ico`,
`_eval_act`, and `_eval_nba` functions. We then put these all together with the
corresponding functions that compute the respective trigger expressions into
the top level `_eval` function, which on the high level has the form:
::
void _eval() {
// Update combinational logic dependent on top level inptus ('ico' region)
while (true) {
_eval__triggers__ico();
// If no 'ico' region trigger is active
if (!ico_triggers.any()) break;
_eval_ico();
}
// Iterate 'act' and 'nba' regions together
while (true) {
// Iterate 'act' region, this computes all derived clocks updaed in the
// Active scheduling region, but does not commit any NBAs that executed
// in 'act' region logic.
while (true) {
_eval__triggers__act();
// If no 'act' region trigger is active
if (!act_triggers.any()) break;
// Remember what 'act' triggers were active, 'nba' uses the same
latch_act_triggers_for_nba();
_eval_act();
}
// If no 'nba' region trigger is active
if (!nba_triggers.any()) break;
// Evaluate all other Active region logic, and commti NBAs
_eval_nba();
}
}
Multithreaded Mode
------------------

View File

@ -29,6 +29,7 @@
#endif
#include <algorithm>
#include <array>
#include <deque>
#include <map>
#include <set>
@ -70,6 +71,67 @@ extern std::string VL_TO_STRING_W(int words, const WDataInP obj);
#define VL_OUT(name, msb, lsb) IData name ///< Declare output signal, 17-32 bits
#define VL_OUTW(name, msb, lsb, words) VlWide<words> name ///< Declare output signal, 65+ bits
//===================================================================
// Activity trigger vector
template <std::size_t T_size> //
class VlTriggerVec final {
private:
// MEMBERS
std::array<bool, T_size> m_flags; // State of the assoc array
public:
// CONSTRUCTOR
VlTriggerVec() { clear(); }
~VlTriggerVec() = default;
// METHODS
// Set all elements to false
void clear() { m_flags.fill(false); }
// Reference to element at 'index'
bool& at(size_t index) { return m_flags.at(index); }
// Return true iff at least one element is set
bool any() const {
for (size_t i = 0; i < T_size; ++i)
if (m_flags[i]) return true;
return false;
}
// Set all elements true in 'this' that are set in 'other'
void set(const VlTriggerVec<T_size>& other) {
for (size_t i = 0; i < T_size; ++i) m_flags[i] |= other.m_flags[i];
}
// Set elements of 'this' to 'a & !b' element-wise
void andNot(const VlTriggerVec<T_size>& a, const VlTriggerVec<T_size>& b) {
for (size_t i = 0; i < T_size; ++i) m_flags[i] = a.m_flags[i] & !b.m_flags[i];
}
};
//===================================================================
// SystemVerilog event type
class VlEvent final {
// MEMBERS
bool m_fired = false; // Fired on this scheduling iteration
bool m_triggered = false; // Triggered state of event persisting until next time step
public:
// CONSTRUCTOR
VlEvent() = default;
~VlEvent() = default;
// METHODS
void fire() { m_fired = m_triggered = true; }
bool isFired() const { return m_fired; }
bool isTriggered() const { return m_triggered; }
void clearFired() { m_fired = false; }
void clearTriggered() { m_triggered = false; }
};
//===================================================================
// Shuffle RNG
@ -860,6 +922,13 @@ template <class T_Value, std::size_t T_Depth> struct VlUnpacked final {
T_Value& operator[](size_t index) { return m_storage[index]; }
const T_Value& operator[](size_t index) const { return m_storage[index]; }
bool operator!=(const VlUnpacked<T_Value, T_Depth>& that) const {
for (int i = 0; i < T_Depth; ++i) {
if (m_storage[i] != that.m_storage[i]) return true;
}
return false;
}
// Dumping. Verilog: str = $sformatf("%p", assoc)
std::string to_string() const {
std::string out = "'{";

View File

@ -166,7 +166,6 @@ RAW_OBJS = \
V3Case.o \
V3Cast.o \
V3Cdc.o \
V3Changed.o \
V3Class.o \
V3Clean.o \
V3Clock.o \
@ -201,7 +200,6 @@ RAW_OBJS = \
V3FileLine.o \
V3Force.o \
V3Gate.o \
V3GenClk.o \
V3Global.o \
V3Graph.o \
V3GraphAlg.o \
@ -239,6 +237,10 @@ RAW_OBJS = \
V3ProtectLib.o \
V3Randomize.o \
V3Reloop.o \
V3Sched.o \
V3SchedAcyclic.o \
V3SchedPartition.o \
V3SchedReplicate.o \
V3Scope.o \
V3Scoreboard.o \
V3Slice.o \

View File

@ -206,8 +206,10 @@ class ActiveNamer final : public ActiveBaseVisitor {
private:
// STATE
AstScope* m_scopep = nullptr; // Current scope to add statement to
AstActive* m_iActivep = nullptr; // For current scope, the IActive we're building
AstActive* m_cActivep = nullptr; // For current scope, the SActive(combo) we're building
AstActive* m_sActivep = nullptr; // For current scope, the Static active we're building
AstActive* m_iActivep = nullptr; // For current scope, the Initial active we're building
AstActive* m_fActivep = nullptr; // For current scope, the Final active we're building
AstActive* m_cActivep = nullptr; // For current scope, the Combo active we're building
// Map from AstSenTree (equivalence) to the corresponding AstActive created.
std::unordered_map<VNRef<AstSenTree>, AstActive*> m_activeMap;
@ -217,10 +219,13 @@ private:
UASSERT_OBJ(m_scopep, nodep, "nullptr scope");
m_scopep->addActivep(nodep);
}
// VISITORS
virtual void visit(AstScope* nodep) override {
m_scopep = nodep;
m_sActivep = nullptr;
m_iActivep = nullptr;
m_fActivep = nullptr;
m_cActivep = nullptr;
m_activeMap.clear();
iterateChildren(nodep);
@ -234,30 +239,28 @@ private:
virtual void visit(AstNodeStmt*) override {} // Accelerate
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
// Specialized below for the special sensitivity classes
template <typename SenItemKind> AstActive*& getSpecialActive();
public:
// METHODS
AstScope* scopep() { return m_scopep; }
AstActive* getCActive(FileLine* fl) {
if (!m_cActivep) {
m_cActivep = new AstActive(
fl, "combo", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Combo())));
m_cActivep->sensesStorep(m_cActivep->sensesp());
addActive(m_cActivep);
// Return an AstActive sensitive to the given special sensitivity class
template <typename SenItemKind> AstActive* getSpecialActive(FileLine* fl) {
AstActive*& cachep = getSpecialActive<SenItemKind>();
if (!cachep) {
AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}};
cachep = new AstActive{fl, "", senTreep};
cachep->sensesStorep(cachep->sensesp());
addActive(cachep);
}
return m_cActivep;
}
AstActive* getIActive(FileLine* fl) {
if (!m_iActivep) {
m_iActivep = new AstActive(
fl, "initial", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Initial())));
m_iActivep->sensesStorep(m_iActivep->sensesp());
addActive(m_iActivep);
}
return m_iActivep;
return cachep;
}
// Return an AstActive that is sensitive to a SenTree equivalent to the given sentreep.
AstActive* getActive(FileLine* fl, AstSenTree* sensesp) {
UASSERT(sensesp, "Must be non-null");
auto it = m_activeMap.find(*sensesp);
// If found matching AstActive, return it
@ -278,6 +281,11 @@ public:
void main(AstScope* nodep) { iterate(nodep); }
};
template <> AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Static>() { return m_sActivep; }
template <> AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Initial>() { return m_iActivep; }
template <> AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Final>() { return m_fActivep; }
template <> AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Combo>() { return m_cActivep; }
//######################################################################
// Latch checking visitor
@ -312,10 +320,10 @@ private:
public:
// CONSTRUCTORS
ActiveLatchCheckVisitor(AstNode* nodep, VAlwaysKwd kwd) {
ActiveLatchCheckVisitor(AstNode* nodep, bool expectLatch) {
m_graph.begin();
iterate(nodep);
m_graph.latchCheck(nodep, kwd == VAlwaysKwd::ALWAYS_LATCH);
m_graph.latchCheck(nodep, expectLatch);
}
virtual ~ActiveLatchCheckVisitor() = default;
};
@ -397,87 +405,24 @@ class ActiveVisitor final : public ActiveBaseVisitor {
private:
// NODE STATE
// Each call to V3Const::constify
// AstVarScope::user1() bool: This VarScope is referenced in the sensitivity list
// AstVarScope::user2() bool: This VarScope is written in the current process
// AstNode::user4() Used by V3Const::constify, called below
// STATE
ActiveNamer m_namer; // Tracking of active names
AstCFunc* m_scopeFinalp = nullptr; // Final function for this scope
bool m_itemCombo = false; // Found a SenItem combo
bool m_itemSequent = false; // Found a SenItem sequential
// VISITORS
virtual void visit(AstScope* nodep) override {
// Create required actives and add to scope
UINFO(4, " SCOPE " << nodep << endl);
// Clear last scope's names, and collect this scope's existing names
m_namer.main(nodep);
m_scopeFinalp = nullptr;
iterateChildren(nodep);
}
virtual void visit(AstActive* nodep) override {
// Actives are being formed, so we can ignore any already made
}
virtual void visit(AstInitialStatic* nodep) override {
// Relink to IACTIVE, unless already under it
UINFO(4, " INITIAL " << nodep << endl);
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
AstActive* const wantactivep = m_namer.getIActive(nodep->fileline());
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
}
virtual void visit(AstInitial* nodep) override {
// Relink to IACTIVE, unless already under it
UINFO(4, " INITIAL " << nodep << endl);
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
AstActive* const wantactivep = m_namer.getIActive(nodep->fileline());
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
}
virtual void visit(AstAssignAlias* nodep) override {
// Relink to CACTIVE, unless already under it
UINFO(4, " ASSIGNW " << nodep << endl);
AstActive* const wantactivep = m_namer.getCActive(nodep->fileline());
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
}
virtual void visit(AstAssignW* nodep) override {
// Relink to CACTIVE, unless already under it
UINFO(4, " ASSIGNW " << nodep << endl);
AstActive* const wantactivep = m_namer.getCActive(nodep->fileline());
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
}
virtual void visit(AstCoverToggle* nodep) override {
// Relink to CACTIVE, unless already under it
UINFO(4, " COVERTOGGLE " << nodep << endl);
AstActive* const wantactivep = m_namer.getCActive(nodep->fileline());
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
}
virtual void visit(AstFinal* nodep) override {
// Relink to CFUNC for the final
UINFO(4, " FINAL " << nodep << endl);
if (!nodep->bodysp()) { // Empty, Kill it.
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
if (!m_scopeFinalp) {
m_scopeFinalp = new AstCFunc(
nodep->fileline(), "_final_" + m_namer.scopep()->nameDotless(), m_namer.scopep());
m_scopeFinalp->dontCombine(true);
m_scopeFinalp->isFinal(true);
m_scopeFinalp->isStatic(false);
m_scopeFinalp->isLoose(true);
m_scopeFinalp->slow(true);
m_namer.scopep()->addActivep(m_scopeFinalp);
}
nodep->unlinkFrBack();
m_scopeFinalp->addStmtsp(nodep->bodysp()->unlinkFrBackWithNext());
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
bool m_clockedProcess = false; // Whether current process is a clocked process
bool m_allChanged = false; // Whether all SenItem in the SenTree are ET_CHANGED
bool m_walkingBody = false; // Walking body of a process
bool m_canBeComb = false; // Whether current clocked process can be turned into a comb process
// METHODS
template <typename T> void moveUnderSpecial(AstNode* nodep) {
AstActive* const wantactivep = m_namer.getSpecialActive<T>(nodep->fileline());
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
}
void visitAlways(AstNode* nodep, AstSenTree* oldsensesp, VAlwaysKwd kwd) {
// Move always to appropriate ACTIVE based on its sense list
if (oldsensesp && oldsensesp->sensesp() && oldsensesp->sensesp()->isNever()) {
@ -488,112 +433,152 @@ private:
return;
}
// Read sensitivities
m_itemCombo = false;
m_itemSequent = false;
iterateAndNextNull(oldsensesp);
bool combo = m_itemCombo;
bool sequent = m_itemSequent;
{
const VNUser1InUse user1InUse;
if (!combo && !sequent) combo = true; // If no list, Verilog 2000: always @ (*)
if (combo && sequent) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Mixed edge (pos/negedge) and activity "
"(no edge) sensitive activity list");
sequent = false;
}
AstActive* wantactivep = nullptr;
if (combo && !sequent) {
// Combo: Relink to ACTIVE(combo)
wantactivep = m_namer.getCActive(nodep->fileline());
} else {
// Sequential: Build a ACTIVE(name)
// OPTIMIZE: We could substitute a constant for things in the sense list, for example
// always (posedge RESET) { if (RESET).... } we know RESET is true.
// Summarize a long list of combo inputs as just "combo"
#ifndef __COVERITY__ // Else dead code on next line.
if (combo) {
oldsensesp->addSensesp(new AstSenItem(nodep->fileline(), AstSenItem::Combo()));
// Walk sensitivity list
m_clockedProcess = false;
m_allChanged = true;
if (oldsensesp) {
oldsensesp->unlinkFrBack();
iterateChildrenConst(oldsensesp);
}
// If all SenItems are ET_CHANGE, then walk the body to determine if this process
// could be turned into a combinational process instead.
if (m_allChanged) {
const VNUser2InUse user2InUse;
m_walkingBody = true;
m_canBeComb = true;
iterateChildrenConst(nodep);
m_walkingBody = false;
if (m_canBeComb) m_clockedProcess = false;
}
#endif
wantactivep = m_namer.getActive(nodep->fileline(), oldsensesp);
}
AstActive* const wantactivep
= m_clockedProcess ? m_namer.getActive(nodep->fileline(), oldsensesp)
: m_namer.getSpecialActive<AstSenItem::Combo>(nodep->fileline());
// Delete sensitivity list
if (oldsensesp) {
VL_DO_DANGLING(oldsensesp->unlinkFrBackWithNext()->deleteTree(), oldsensesp);
}
if (oldsensesp) VL_DO_DANGLING(oldsensesp->deleteTree(), oldsensesp);
// Move node to new active
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
// Warn and/or convert any delayed assignments
if (combo && !sequent) {
ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB};
const ActiveLatchCheckVisitor latchvisitor{nodep, kwd};
} else if (!combo && sequent) {
ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_SEQ};
// Warn and convert any delayed assignments
ActiveDlyVisitor{nodep,
m_clockedProcess ? ActiveDlyVisitor::CT_SEQ : ActiveDlyVisitor::CT_COMB};
// check combinational processes for latches
if (!m_clockedProcess || kwd == VAlwaysKwd::ALWAYS_LATCH) {
const ActiveLatchCheckVisitor latchvisitor{nodep, kwd == VAlwaysKwd::ALWAYS_LATCH};
}
}
virtual void visit(AstAlways* nodep) override {
// Move always to appropriate ACTIVE based on its sense list
UINFO(4, " ALW " << nodep << endl);
// if (debug() >= 9) nodep->dumpTree(cout, " Alw: ");
if (!nodep->bodysp()) {
// Empty always. Kill it.
// VISITORS
virtual void visit(AstScope* nodep) override {
m_namer.main(nodep); // Clear last scope's names, and collect this scope's existing names
iterateChildren(nodep);
}
virtual void visit(AstActive* nodep) override {
// Actives are being formed, so we can ignore any already made
}
virtual void visit(AstInitialStatic* nodep) override {
moveUnderSpecial<AstSenItem::Static>(nodep);
}
virtual void visit(AstInitial* nodep) override {
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
moveUnderSpecial<AstSenItem::Initial>(nodep);
}
virtual void visit(AstFinal* nodep) override {
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL};
moveUnderSpecial<AstSenItem::Final>(nodep);
}
virtual void visit(AstAssignAlias* nodep) override {
moveUnderSpecial<AstSenItem::Combo>(nodep);
}
virtual void visit(AstCoverToggle* nodep) override {
moveUnderSpecial<AstSenItem::Combo>(nodep);
}
virtual void visit(AstAssignW* nodep) override {
visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS_COMB);
}
virtual void visit(AstAlways* nodep) override {
if (!nodep->bodysp()) { // Empty always. Remove it now.
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
visitAlways(nodep, nodep->sensesp(), nodep->keyword());
}
virtual void visit(AstAlwaysPostponed* nodep) override {
UINFO(4, " ALW " << nodep << endl);
if (!nodep->bodysp()) {
if (!nodep->bodysp()) { // Empty always. Remove it now.
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS);
}
virtual void visit(AstAlwaysPublic* nodep) override {
// Move always to appropriate ACTIVE based on its sense list
UINFO(4, " ALWPub " << nodep << endl);
// if (debug() >= 9) nodep->dumpTree(cout, " Alw: ");
visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS);
}
virtual void visit(AstSenItem* nodep) override {
UASSERT_OBJ(!m_walkingBody, nodep, "Should not reach here when walking body");
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 (basicp->isEventValue()) {
// Events need to be treated as active high so we only activate on event being
// 1
UINFO(8, "Demote event to HIGHEDGE " << nodep << endl);
nodep->edgeType(VEdgeType::ET_HIGHEDGE);
}
if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT);
}
}
if (nodep->edgeType() == VEdgeType::ET_ANYEDGE) {
m_itemCombo = true;
// Delete the sensitivity
// We'll add it as a generic COMBO SenItem in a moment.
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->varrefp()) {
// V3LinkResolve should have cleaned most of these up
if (!nodep->varrefp()->width1()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Non-single bit wide signal pos/negedge sensitivity: "
<< nodep->varrefp()->prettyNameQ());
}
m_itemSequent = true;
nodep->varrefp()->varp()->usedClock(true);
nodep->sensp()->foreach<AstVarRef>([](const AstVarRef* refp) {
refp->varp()->usedClock(true);
refp->varScopep()->user1(true);
});
}
virtual void visit(AstVarRef* nodep) override {
AstVarScope* const vscp = nodep->varScopep();
if (nodep->access().isWriteOnly()) {
vscp->user2(true);
} else {
// If the variable is read before it is written, and is not in the sensitivity list,
// then this cannot be optimized into a combinational process
// TODO: live variable analysis would be more precise
if (!vscp->user2() && !vscp->user1()) m_canBeComb = false;
}
}
virtual void visit(AstAssignDly* nodep) override {
m_canBeComb = false;
iterateChildrenConst(nodep);
}
virtual void visit(AstFireEvent* nodep) override {
m_canBeComb = false;
iterateChildrenConst(nodep);
}
virtual void visit(AstAssignForce* nodep) override {
m_canBeComb = false;
iterateChildrenConst(nodep);
}
virtual void visit(AstRelease* nodep) override {
m_canBeComb = false;
iterateChildrenConst(nodep);
}
//--------------------
virtual void visit(AstNodeMath*) override {} // Accelerate
virtual void visit(AstVar*) override {} // Accelerate
virtual void visit(AstVarScope*) override {} // Accelerate
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
virtual void visit(AstNode* nodep) override {
if (m_walkingBody && !m_canBeComb) return; // Accelerate
if (!nodep->isPure()) m_canBeComb = false;
iterateChildren(nodep);
}
public:
// CONSTRUCTORS

View File

@ -36,20 +36,28 @@
// Active class functions
class ActiveTopVisitor final : public VNVisitor {
private:
// NODE STATE
// Entire netlist
// AstNode::user() bool. True if processed
// Each call to V3Const::constify
// AstNode::user4() Used by V3Const::constify, called below
const VNUser1InUse m_inuser1;
// STATE
SenTreeFinder m_finder; // Find global sentree's / add them under the AstTopScope
// METHODS
VL_DEBUG_FUNC; // Declare debug()
static bool isInitial(AstNode* nodep) {
const VNUser1InUse user1InUse;
// Return true if no variables that read.
return nodep->forall<AstVarRef>([&](const AstVarRef* refp) -> bool {
AstVarScope* const vscp = refp->varScopep();
// Note: Use same heuristic as ordering does to ignore written variables
// TODO: Use live variable analysis.
if (refp->access().isWriteOnly()) {
vscp->user1(true);
return true;
}
// Read or ReadWrite: OK if written before
return vscp->user1();
});
}
// VISITORS
virtual void visit(AstNodeModule* nodep) override {
// Create required actives and add to module
@ -70,15 +78,6 @@ private:
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
// Copy combo tree to settlement tree with duplicated statements
if (sensesp->hasCombo()) {
AstSenTree* const newsentreep = new AstSenTree(
nodep->fileline(), new AstSenItem(nodep->fileline(), AstSenItem::Settle()));
AstActive* const newp = new AstActive(nodep->fileline(), "settle", newsentreep);
newp->sensesStorep(newsentreep);
if (nodep->stmtsp()) newp->addStmtsp(nodep->stmtsp()->cloneTree(true));
nodep->addNextHere(newp);
}
// Move the SENTREE for each active up to the global level.
// This way we'll easily see what clock domains are identical
AstSenTree* const wantp = m_finder.getSenTree(sensesp);
@ -97,8 +96,23 @@ private:
}
nodep->sensesp(wantp);
}
// No need to do statements under it, they're already moved.
// iterateChildren(nodep);
// If this is combinational logic that does not read any variables, then it really is an
// initial block in disguise, so move such logic under an Initial AstActive, V3Order would
// prune these otherwise.
// TODO: we should warn for these if they were 'always @*' as some (including strictly
// compliant) simulators will never execute these.
if (nodep->sensesp()->hasCombo()) {
FileLine* const flp = nodep->fileline();
AstActive* initialp = nullptr;
for (AstNode *logicp = nodep->stmtsp(), *nextp; logicp; logicp = nextp) {
nextp = logicp->nextp();
if (!isInitial(logicp)) continue;
if (!initialp) initialp = new AstActive{flp, "", m_finder.getInitial()};
initialp->addStmtsp(logicp->unlinkFrBack());
}
if (initialp) nodep->addHereThisAsNext(initialp);
}
}
virtual void visit(AstNodeProcedure* nodep) override { // LCOV_EXCL_LINE
nodep->v3fatalSrc("Node should have been under ACTIVE");

View File

@ -261,66 +261,87 @@ public:
// in V3Const::visit AstSenTree
ET_ILLEGAL,
// Involving a variable
ET_ANYEDGE, // Default for sensitivities; rip them out
ET_BOTHEDGE, // POSEDGE | NEGEDGE
ET_CHANGED, // Value changed
ET_BOTHEDGE, // POSEDGE | NEGEDGE (i.e.: 'edge' in Verilog)
ET_POSEDGE,
ET_NEGEDGE,
ET_HIGHEDGE, // Is high now (latches)
ET_LOWEDGE, // Is low now (latches)
// Not involving anything
ET_EVENT, // VlEvent::isFired
ET_DPIEXPORT, // Used exclusively to check the AstNetlist::dpiExportTriggerp()
// Involving an expression
ET_TRUE,
//
ET_COMBO, // Sensitive to all combo inputs to this block
ET_INITIAL, // User initial statements
ET_SETTLE, // Like combo but for initial wire resolutions after initial statement
ET_HYBRID, // This is like ET_COMB, but with explicit sensitivity to an expression
ET_STATIC, // static variable initializers (runs before 'initial')
ET_INITIAL, // 'initial' statements
ET_FINAL, // 'final' statements
ET_NEVER // Never occurs (optimized away)
};
enum en m_e;
bool clockedStmt() const {
static const bool clocked[]
= {false, false, true, true, true, true, true, false, false, false};
static const bool clocked[] = {
false, // ET_ILLEGAL
true, // ET_CHANGED
true, // ET_BOTHEDGE
true, // ET_POSEDGE
true, // ET_NEGEDGE
true, // ET_EVENT
true, // ET_DPIEXPORT
true, // ET_TRUE
false, // ET_COMBO
false, // ET_HYBRID
false, // ET_STATIC
false, // ET_INITIAL
false, // ET_FINAL
false, // ET_NEVER
};
return clocked[m_e];
}
VEdgeType invert() const {
switch (m_e) {
case ET_ANYEDGE: return ET_ANYEDGE;
case ET_CHANGED: return ET_CHANGED;
case ET_BOTHEDGE: return ET_BOTHEDGE;
case ET_POSEDGE: return ET_NEGEDGE;
case ET_NEGEDGE: return ET_POSEDGE;
case ET_HIGHEDGE: return ET_LOWEDGE;
case ET_LOWEDGE: return ET_HIGHEDGE;
default: UASSERT_STATIC(0, "Inverting bad edgeType()");
}
return VEdgeType::ET_ILLEGAL;
}
const char* ascii() const {
static const char* const names[]
= {"%E-edge", "ANY", "BOTH", "POS", "NEG", "HIGH",
"LOW", "COMBO", "INITIAL", "SETTLE", "NEVER"};
static const char* const names[] = {"%E-edge",
"CHANGED",
"BOTH",
"POS",
"NEG",
"EVENT",
"DPIEXPORT"
"TRUE",
"COMBO",
"HYBRID",
"STATIC",
"INITIAL",
"FINAL",
"NEVER"};
return names[m_e];
}
const char* verilogKwd() const {
static const char* const names[]
= {"%E-edge", "[any]", "edge", "posedge", "negedge", "[high]",
"[low]", "*", "[initial]", "[settle]", "[never]"};
static const char* const names[] = {
"%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[dpiexport]",
"[true]", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
return names[m_e];
}
// Return true iff this and the other have mutually exclusive transitions
bool exclusiveEdge(const VEdgeType& other) const {
switch (m_e) {
case VEdgeType::ET_POSEDGE:
switch (other.m_e) {
case VEdgeType::ET_NEGEDGE: // FALLTHRU
case VEdgeType::ET_LOWEDGE: return true;
default:;
}
if (other.m_e == VEdgeType::ET_NEGEDGE) return true;
break;
case VEdgeType::ET_NEGEDGE:
switch (other.m_e) {
case VEdgeType::ET_POSEDGE: // FALLTHRU
case VEdgeType::ET_HIGHEDGE: return true;
default:;
}
if (other.m_e == VEdgeType::ET_POSEDGE) return true;
break;
default:;
default: break;
}
return false;
}
@ -371,7 +392,7 @@ public:
TYPENAME, // V3Width processes
//
VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes
VAR_CLOCK_ENABLE, // V3LinkParse moves to AstVar::attrClockEn
VAR_CLOCK_ENABLE, // Ignored, accepted for compatibility
VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
@ -427,7 +448,7 @@ public:
BIT,
BYTE,
CHANDLE,
EVENTVALUE, // See comments in t_event_copy as to why this is EVENTVALUE
EVENT,
INT,
INTEGER,
LOGIC,
@ -441,6 +462,7 @@ public:
SCOPEPTR,
CHARPTR,
MTASKSTATE,
TRIGGERVEC,
// Unsigned and two state; fundamental types
UINT32,
UINT64,
@ -452,18 +474,20 @@ 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", "IData", "QData", "LOGIC_IMPLICIT", " MAX"};
= {"%E-unk", "bit", "byte", "chandle", "event",
"int", "integer", "logic", "longint", "real",
"shortint", "time", "string", "VerilatedScope*", "char*",
"VlMTaskState", "VlTriggerVec", "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", "IData", "QData", "%E-logic-implct", " MAX"};
= {"%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"};
return names[m_e];
}
static void selfTest() {
@ -484,7 +508,7 @@ public:
case BIT: return 1; // scalar, can't bit extract unless ranged
case BYTE: return 8;
case CHANDLE: return 64;
case EVENTVALUE: return 1;
case EVENT: return 1;
case INT: return 32;
case INTEGER: return 32;
case LOGIC: return 1; // scalar, can't bit extract unless ranged
@ -496,6 +520,7 @@ public:
case SCOPEPTR: return 0; // opaque
case CHARPTR: return 0; // opaque
case MTASKSTATE: return 0; // opaque
case TRIGGERVEC: return 0; // opaque
case UINT32: return 32;
case UINT64: return 64;
default: return 0;
@ -506,15 +531,14 @@ public:
|| m_e == DOUBLE;
}
bool isUnsigned() const {
return m_e == CHANDLE || m_e == EVENTVALUE || m_e == STRING || m_e == SCOPEPTR
|| m_e == CHARPTR || m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC
|| m_e == TIME;
return m_e == CHANDLE || m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|| m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC || m_e == TIME;
}
bool isFourstate() const {
return m_e == INTEGER || m_e == LOGIC || m_e == LOGIC_IMPLICIT || m_e == TIME;
}
bool isZeroInit() const { // Otherwise initializes to X
return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == EVENTVALUE || m_e == INT
return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == EVENT || m_e == INT
|| m_e == LONGINT || m_e == SHORTINT || m_e == STRING || m_e == DOUBLE);
}
bool isIntNumeric() const { // Enum increment supported
@ -532,11 +556,11 @@ public:
|| m_e == DOUBLE || m_e == SHORTINT || m_e == UINT32 || m_e == UINT64);
}
bool isOpaque() const { // IE not a simple number we can bit optimize
return (m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR || m_e == MTASKSTATE
|| m_e == DOUBLE);
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DOUBLE);
}
bool isDouble() const { return m_e == DOUBLE; }
bool isEventValue() const { return m_e == EVENTVALUE; }
bool isEvent() const { return m_e == EVENT; }
bool isString() const { return m_e == STRING; }
bool isMTaskState() const { return m_e == MTASKSTATE; }
// Does this represent a C++ LiteralType? (can be constexpr)
@ -2113,6 +2137,15 @@ template <> inline bool AstNode::privateMayBeUnder<AstExecGraph>(const AstNode*
if (VN_IS(nodep, NodeStmt)) return false; // Should be directly under CFunc
return true;
}
template <> inline bool AstNode::privateMayBeUnder<AstActive>(const AstNode* nodep) {
return !VN_IS(nodep, Active); // AstActives do not nest
}
template <> inline bool AstNode::privateMayBeUnder<AstScope>(const AstNode* nodep) {
return !VN_IS(nodep, Scope); // AstScopes do not nest
}
template <> inline bool AstNode::privateMayBeUnder<AstSenTree>(const AstNode* nodep) {
return !VN_IS(nodep, SenTree); // AstSenTree do not nest
}
inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) {
if (!rhs) {
@ -2137,7 +2170,7 @@ public:
VNRef(U&& x)
: std::reference_wrapper<T_Node>{x} {}
VNRef(const VNRef& other) noexcept
VNRef(const std::reference_wrapper<T_Node>& other)
: std::reference_wrapper<T_Node>{other} {}
};
@ -2655,6 +2688,7 @@ public:
return text() == asamep->text();
}
const string& text() const { return m_text; }
void text(const string& value) { m_text = value; }
};
class AstNodeDType VL_NOT_FINAL : public AstNode {

View File

@ -706,6 +706,10 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
info.m_type = "std::string";
} else if (bdtypep->keyword().isMTaskState()) {
info.m_type = "VlMTaskVertex";
} else if (bdtypep->isTriggerVec()) {
info.m_type = "VlTriggerVec<" + cvtToStr(dtypep->width()) + ">";
} else if (bdtypep->isEvent()) {
info.m_type = "VlEvent";
} else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width
info.m_type = "CData" + bitvec;
} else if (dtypep->widthMin() <= 16) {
@ -854,6 +858,29 @@ string AstScope::nameDotless() const {
return out;
}
AstVarScope* AstScope::createTemp(const string& name, unsigned width) {
FileLine* const flp = fileline();
AstVar* const varp
= new AstVar{flp, VVarType::MODULETEMP, name, VFlagBitPacked{}, static_cast<int>(width)};
modp()->addStmtp(varp);
AstVarScope* const vscp = new AstVarScope{flp, this, varp};
addVarp(vscp);
return vscp;
}
AstVarScope* AstScope::createTemp(const string& name, AstNodeDType* dtypep) {
FileLine* const flp = fileline();
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
modp()->addStmtp(varp);
AstVarScope* const vscp = new AstVarScope{flp, this, varp};
addVarp(vscp);
return vscp;
}
AstVarScope* AstScope::createTempLike(const string& name, AstVarScope* vscp) {
return createTemp(name, vscp->dtypep());
}
string AstScopeName::scopePrettyNameFormatter(AstText* scopeTextp) const {
string out;
for (AstText* textp = scopeTextp; textp; textp = VN_AS(textp->nextp(), Text)) {
@ -886,10 +913,10 @@ bool AstSenTree::hasClocked() const {
}
return false;
}
bool AstSenTree::hasSettle() const {
bool AstSenTree::hasStatic() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isSettle()) return true;
if (senp->isStatic()) return true;
}
return false;
}
@ -900,6 +927,13 @@ bool AstSenTree::hasInitial() const {
}
return false;
}
bool AstSenTree::hasFinal() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isFinal()) return true;
}
return false;
}
bool AstSenTree::hasCombo() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
@ -907,6 +941,13 @@ bool AstSenTree::hasCombo() const {
}
return false;
}
bool AstSenTree::hasHybrid() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isHybrid()) return true;
}
return false;
}
AstTypeTable::AstTypeTable(FileLine* fl)
: ASTGEN_SUPER_TypeTable(fl) {
@ -1738,7 +1779,6 @@ void AstVar::dump(std::ostream& str) const {
if (isSigPublic()) str << " [P]";
if (isLatched()) str << " [LATCHED]";
if (isUsedLoopIdx()) str << " [LOOP]";
if (attrClockEn()) str << " [aCLKEN]";
if (attrIsolateAssign()) str << " [aISO]";
if (attrFileDescr()) str << " [aFD]";
if (isFuncReturn()) {

View File

@ -934,7 +934,8 @@ public:
}
bool isBitLogic() const { return keyword().isBitLogic(); }
bool isDouble() const { return keyword().isDouble(); }
bool isEventValue() const { return keyword().isEventValue(); }
bool isEvent() const { return keyword() == VBasicDTypeKwd::EVENT; }
bool isTriggerVec() const { return keyword() == VBasicDTypeKwd::TRIGGERVEC; }
bool isOpaque() const { return keyword().isOpaque(); }
bool isString() const { return keyword().isString(); }
bool isZeroInit() const { return keyword().isZeroInit(); }
@ -1979,7 +1980,6 @@ private:
bool m_usedLoopIdx : 1; // Variable subject of for unrolling
bool m_funcLocal : 1; // Local variable for a function
bool m_funcReturn : 1; // Return variable for a function
bool m_attrClockEn : 1; // User clock enable attribute
bool m_attrScBv : 1; // User force bit vector attribute
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
bool m_attrSFormat : 1; // User sformat attribute
@ -2019,7 +2019,6 @@ private:
m_sigUserRWPublic = false;
m_funcLocal = false;
m_funcReturn = false;
m_attrClockEn = false;
m_attrScBv = false;
m_attrIsolateAssign = false;
m_attrSFormat = false;
@ -2157,7 +2156,6 @@ public:
virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); }
void ansi(bool flag) { m_ansi = flag; }
void declTyped(bool flag) { m_declTyped = flag; }
void attrClockEn(bool flag) { m_attrClockEn = flag; }
void attrClocker(VVarAttrClocker flag) { m_attrClocker = flag; }
void attrFileDescr(bool flag) { m_fileDescr = flag; }
void attrScClocked(bool flag) { m_scClocked = flag; }
@ -2262,7 +2260,6 @@ public:
bool isFuncReturn() const { return m_funcReturn; }
bool isPullup() const { return m_isPullup; }
bool isPulldown() const { return m_isPulldown; }
bool attrClockEn() const { return m_attrClockEn; }
bool attrScBv() const { return m_attrScBv; }
bool attrFileDescr() const { return m_fileDescr; }
bool attrScClocked() const { return m_scClocked; }
@ -2276,14 +2273,13 @@ public:
void propagateAttrFrom(AstVar* fromp) {
// This is getting connected to fromp; keep attributes
// Note the method below too
if (fromp->attrClockEn()) attrClockEn(true);
if (fromp->attrFileDescr()) attrFileDescr(true);
if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
if (fromp->isContinuously()) isContinuously(true);
}
bool gateMultiInputOptimizable() const {
// Ok to gate optimize; must return false if propagateAttrFrom would do anything
return (!attrClockEn() && !isUsedClock());
return !isUsedClock();
}
void combineType(AstVar* typevarp) {
// This is same as typevarp (for combining input & reg decls)
@ -2372,15 +2368,20 @@ public:
string nameDotless() const;
string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); }
AstNodeModule* modp() const { return m_modp; }
// op1: AstVarScope's
AstVarScope* varsp() const { return VN_AS(op1p(), VarScope); }
void addVarp(AstVarScope* nodep) { addOp1p((AstNode*)nodep); }
AstVarScope* varsp() const { return VN_AS(op1p(), VarScope); } // op1 = AstVarScope's
// op2: Logic blocks/AstActive/AstExecGraph
AstNode* blocksp() const { return op2p(); }
void addActivep(AstNode* nodep) { addOp2p(nodep); }
AstNode* blocksp() const { return op2p(); } // op2 = Block names
void addFinalClkp(AstNode* nodep) { addOp3p(nodep); }
AstNode* finalClksp() const { return op3p(); } // op3 = Final assigns for clock correction
//
AstScope* aboveScopep() const { return m_aboveScopep; }
AstCell* aboveCellp() const { return m_aboveCellp; }
bool isTop() const { return aboveScopep() == nullptr; } // At top of hierarchy
// Create new MODULETEMP variable under this scope
AstVarScope* createTemp(const string& name, unsigned width);
AstVarScope* createTemp(const string& name, AstNodeDType* dtypep);
AstVarScope* createTempLike(const string& name, AstVarScope* vscp);
};
class AstTopScope final : public AstNode {
@ -3317,15 +3318,16 @@ class AstSenItem final : public AstNode {
private:
VEdgeType m_edgeType; // Edge type
public:
class Combo {}; // for creator type-overload selection
class Illegal {}; // for creator type-overload selection
class Initial {}; // for creator type-overload selection
class Settle {}; // for creator type-overload selection
class Never {}; // for creator type-overload selection
AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* varrefp)
class Combo {}; // for constructor type-overload selection
class Illegal {}; // for constructor type-overload selection
class Static {}; // for constructor type-overload selection
class Initial {}; // for constructor type-overload selection
class Final {}; // for constructor type-overload selection
class Never {}; // for constructor type-overload selection
AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* senp)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{edgeType} {
setOp1p(varrefp);
setOp1p(senp);
}
AstSenItem(FileLine* fl, Combo)
: ASTGEN_SUPER_SenItem(fl)
@ -3333,12 +3335,15 @@ public:
AstSenItem(FileLine* fl, Illegal)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_ILLEGAL} {}
AstSenItem(FileLine* fl, Static)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_STATIC} {}
AstSenItem(FileLine* fl, Initial)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_INITIAL} {}
AstSenItem(FileLine* fl, Settle)
AstSenItem(FileLine* fl, Final)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_SETTLE} {}
, m_edgeType{VEdgeType::ET_FINAL} {}
AstSenItem(FileLine* fl, Never)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_NEVER} {}
@ -3347,23 +3352,23 @@ public:
virtual bool same(const AstNode* samep) const override {
return edgeType() == static_cast<const AstSenItem*>(samep)->edgeType();
}
VEdgeType edgeType() const { return m_edgeType; } // * = Posedge/negedge
VEdgeType edgeType() const { return m_edgeType; }
void edgeType(VEdgeType type) {
m_edgeType = type;
editCountInc();
} // * = Posedge/negedge
AstNode* sensp() const { return op1p(); } // op1 = Signal sensitized
AstNodeVarRef* varrefp() const {
return VN_CAST(op1p(), NodeVarRef);
} // op1 = Signal sensitized
}
// op1 = Expression sensitized, if any
AstNode* sensp() const { return op1p(); }
AstNodeVarRef* varrefp() const { return VN_CAST(op1p(), NodeVarRef); }
//
bool isClocked() const { return edgeType().clockedStmt(); }
bool isCombo() const { return edgeType() == VEdgeType::ET_COMBO; }
bool isHybrid() const { return edgeType() == VEdgeType::ET_HYBRID; }
bool isStatic() const { return edgeType() == VEdgeType::ET_STATIC; }
bool isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; }
bool isFinal() const { return edgeType() == VEdgeType::ET_FINAL; }
bool isIllegal() const { return edgeType() == VEdgeType::ET_ILLEGAL; }
bool isSettle() const { return edgeType() == VEdgeType::ET_SETTLE; }
bool isNever() const { return edgeType() == VEdgeType::ET_NEVER; }
bool hasVar() const { return !(isCombo() || isInitial() || isSettle() || isNever()); }
};
class AstSenTree final : public AstNode {
@ -3387,9 +3392,11 @@ public:
void multi(bool flag) { m_multi = true; }
// METHODS
bool hasClocked() const; // Includes a clocked statement
bool hasSettle() const; // Includes a SETTLE SenItem
bool hasStatic() const; // Includes a STATIC SenItem
bool hasInitial() const; // Includes a INITIAL SenItem
bool hasFinal() const; // Includes a FINAL SenItem
bool hasCombo() const; // Includes a COMBO SenItem
bool hasHybrid() const; // Includes a HYBRID SenItem
};
class AstFinal final : public AstNodeProcedure {
@ -3594,6 +3601,20 @@ public:
AstNode* lhsp() const { return op1p(); }
};
class AstFireEvent final : public AstNodeStmt {
// '-> _' and '->> _' event trigger statements
bool m_delayed; // Delayed (->>) vs non-delayed (->)
public:
AstFireEvent(FileLine* fl, AstNode* operandp, bool delayed)
: ASTGEN_SUPER_FireEvent(fl)
, m_delayed{delayed} {
setOp1p(operandp);
}
ASTNODE_NODE_FUNCS(FireEvent);
AstNode* operandp() const { return op1p(); }
bool isDeleyed() const { return m_delayed; }
};
class AstAssignPre final : public AstNodeAssign {
// Like Assign, but predelayed assignment requiring special order handling
public:
@ -4827,24 +4848,6 @@ public:
AstJumpLabel* labelp() const { return m_labelp; }
};
class AstChangeDet final : public AstNodeStmt {
// A comparison to determine change detection, common & must be fast.
public:
// Null lhs+rhs used to indicate change needed with no spec vars
AstChangeDet(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
: ASTGEN_SUPER_ChangeDet(fl) {
setNOp1p(lhsp);
setNOp2p(rhsp);
}
ASTNODE_NODE_FUNCS(ChangeDet)
AstNode* lhsp() const { return op1p(); }
AstNode* rhsp() const { return op2p(); }
virtual bool isGateOptimizable() const override { return false; }
virtual bool isPredictOptimizable() const override { return false; }
virtual int instrCount() const override { return widthInstrs() * 2; } // xor, or/logor
virtual bool same(const AstNode* samep) const override { return true; }
};
class AstConsAssoc final : public AstNodeMath {
// Construct an assoc array and return object, '{}
// Parents: math
@ -5429,10 +5432,7 @@ public:
return nullptr;
}
virtual void cloneRelink() override {
if (m_sensesp->clonep()) {
m_sensesp = m_sensesp->clonep();
UASSERT(m_sensesp, "Bad clone cross link: " << this);
}
if (m_sensesp->clonep()) m_sensesp = m_sensesp->clonep();
}
// Statements are broken into pieces, as some must come before others.
void sensesp(AstSenTree* nodep) { m_sensesp = nodep; }
@ -5440,13 +5440,12 @@ public:
// op1 = Sensitivity tree, if a clocked block in early stages
void sensesStorep(AstSenTree* nodep) { addOp1p(nodep); }
AstSenTree* sensesStorep() const { return VN_AS(op1p(), SenTree); }
// op2 = Combo logic
// op2 = Logic
AstNode* stmtsp() const { return op2p(); }
void addStmtsp(AstNode* nodep) { addOp2p(nodep); }
// METHODS
bool hasInitial() const { return m_sensesp->hasInitial(); }
bool hasSettle() const { return m_sensesp->hasSettle(); }
bool hasClocked() const { return m_sensesp->hasClocked(); }
bool hasCombo() const { return m_sensesp->hasCombo(); }
};
class AstAttrOf final : public AstNode {
@ -8884,7 +8883,6 @@ private:
bool m_isTrace : 1; // Function is related to tracing
bool m_dontCombine : 1; // V3Combine shouldn't compare this func tree, it's special
bool m_declPrivate : 1; // Declare it private
bool m_isFinal : 1; // This is a function corresponding to a SystemVerilog 'final' block
bool m_slow : 1; // Slow routine, called once or just at init time
bool m_funcPublic : 1; // From user public task/function
bool m_isConstructor : 1; // Is C class constructor
@ -8913,7 +8911,6 @@ public:
m_isTrace = false;
m_dontCombine = false;
m_declPrivate = false;
m_isFinal = false;
m_slow = false;
m_funcPublic = false;
m_isConstructor = false;
@ -8971,8 +8968,6 @@ public:
bool dontInline() const { return dontCombine() || slow() || funcPublic(); }
bool declPrivate() const { return m_declPrivate; }
void declPrivate(bool flag) { m_declPrivate = flag; }
bool isFinal() const { return m_isFinal; }
void isFinal(bool flag) { m_isFinal = flag; }
bool slow() const { return m_slow; }
void slow(bool flag) { m_slow = flag; }
bool funcPublic() const { return m_funcPublic; }
@ -9332,11 +9327,11 @@ private:
AstConstPool* const m_constPoolp; // Reference to constant pool, for faster lookup
AstPackage* m_dollarUnitPkgp = nullptr; // $unit
AstCFunc* m_evalp = nullptr; // The '_eval' function
AstCFunc* m_evalNbap = nullptr; // The '_eval__nba' function
AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable
AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module
VTimescale m_timeunit; // Global time unit
VTimescale m_timeprecision; // Global time precision
bool m_changeRequest = false; // Have _change_request method
bool m_timescaleSpecified = false; // Input HDL specified timescale
uint32_t m_nextFreeMTaskID = 1; // Next unique MTask ID within netlist
// starts at 1 so 0 means no MTask ID
@ -9367,8 +9362,6 @@ public:
void addFilesp(AstNodeFile* filep) { addOp2p(filep); }
void addMiscsp(AstNode* nodep) { addOp3p(nodep); }
AstTypeTable* typeTablep() { return m_typeTablep; }
void changeRequest(bool specified) { m_changeRequest = specified; }
bool changeRequest() const { return m_changeRequest; }
AstConstPool* constPoolp() { return m_constPoolp; }
AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; }
AstPackage* dollarUnitPkgAddp() {
@ -9382,8 +9375,11 @@ public:
}
return m_dollarUnitPkgp;
}
AstCFunc* evalp() const { return m_evalp; }
void evalp(AstCFunc* evalp) { m_evalp = evalp; }
void evalp(AstCFunc* funcp) { m_evalp = funcp; }
AstCFunc* evalNbap() const { return m_evalNbap; }
void evalNbap(AstCFunc* funcp) { m_evalNbap = funcp; }
AstVarScope* dpiExportTriggerp() const { return m_dpiExportTriggerp; }
void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; }
AstTopScope* topScopep() const { return m_topScopep; }

View File

@ -185,7 +185,9 @@ void V3CCtors::cctorsAll() {
for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) {
if (AstVar* const varp = VN_CAST(np, Var)) {
if (!varp->isIfaceParent() && !varp->isIfaceRef() && !varp->noReset()
&& !varp->isParam()) {
&& !varp->isParam()
&& !(varp->basicp()
&& (varp->basicp()->isEvent() || varp->basicp()->isTriggerVec()))) {
const auto vrefp = new AstVarRef{varp->fileline(), varp, VAccess::WRITE};
var_reset.add(new AstCReset{varp->fileline(), vrefp});
}

View File

@ -157,8 +157,9 @@ private:
virtual void visit(AstVarRef* nodep) override {
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) && backp->width()
&& castSize(nodep) != castSize(nodep->varp())) {
&& !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor)
&& (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec())
&& 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);
insertCast(nodep, castSize(nodep));

View File

@ -646,8 +646,8 @@ private:
UINFO(4, " BLOCK " << nodep << endl);
AstNode::user2ClearTree();
m_domainp = nodep->sensesp();
if (!m_domainp || m_domainp->hasCombo()
|| m_domainp->hasClocked()) { // IE not hasSettle/hasInitial
// Ignore static initializers, initial and final blocks
if (!m_domainp || m_domainp->hasCombo() || m_domainp->hasClocked()) {
iterateNewStmt(nodep);
}
m_domainp = nullptr;

View File

@ -1,255 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for changed nodes
//
// 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
//
//*************************************************************************
// V3Changed's Transformations:
//
// Each module:
// Each combo block
// For each variable that comes from combo block and is generated AFTER a usage
// Add __Vlast_{var} to local section, init to current value (just use array?)
// Change = if any var != last.
// If a signal is used as a clock in this module or any
// module *below*, and it isn't a input to this module,
// we need to indicate a new clock has been created.
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Global.h"
#include "V3Ast.h"
#include "V3Changed.h"
#include <algorithm>
//######################################################################
class ChangedState final {
public:
// STATE
AstNodeModule* m_topModp = nullptr; // Top module
AstScope* m_scopetopp = nullptr; // Scope under TOPSCOPE
AstCFunc* m_chgFuncp = nullptr; // Change function we're building
AstCFunc* m_tlChgFuncp = nullptr; // Top level change function we're building
int m_numStmts = 0; // Number of statements added to m_chgFuncp
int m_funcNum = 0; // Number of change functions emitted
bool m_madeTopChg = false;
ChangedState() = default;
~ChangedState() = default;
void maybeCreateChgFuncp() {
maybeCreateTopChg();
maybeCreateMidChg();
}
void maybeCreateTopChg() {
if (m_madeTopChg) return;
m_madeTopChg = true;
v3Global.rootp()->changeRequest(true);
// Create a wrapper change detection function that calls each change detection function
m_tlChgFuncp
= new AstCFunc{m_scopetopp->fileline(), "_change_request", m_scopetopp, "QData"};
m_tlChgFuncp->isStatic(false);
m_tlChgFuncp->isLoose(true);
m_tlChgFuncp->declPrivate(true);
m_scopetopp->addActivep(m_tlChgFuncp);
// Each change detection function needs at least one AstChangeDet
// to ensure that V3EmitC outputs the necessary code.
maybeCreateMidChg();
m_chgFuncp->addStmtsp(new AstChangeDet{m_scopetopp->fileline(), nullptr, nullptr});
}
void maybeCreateMidChg() {
// Don't create an extra function call if splitting is disabled
if (!v3Global.opt.outputSplitCFuncs()) {
m_chgFuncp = m_tlChgFuncp;
return;
}
if (!m_chgFuncp || v3Global.opt.outputSplitCFuncs() < m_numStmts) {
m_chgFuncp
= new AstCFunc{m_scopetopp->fileline(), "_change_request_" + cvtToStr(++m_funcNum),
m_scopetopp, "QData"};
m_chgFuncp->isStatic(false);
m_chgFuncp->isLoose(true);
m_chgFuncp->declPrivate(true);
m_scopetopp->addActivep(m_chgFuncp);
// Add a top call to it
AstCCall* const callp = new AstCCall{m_scopetopp->fileline(), m_chgFuncp};
if (!m_tlChgFuncp->stmtsp()) {
m_tlChgFuncp->addStmtsp(new AstCReturn{m_scopetopp->fileline(), callp});
} else {
AstCReturn* const returnp = VN_AS(m_tlChgFuncp->stmtsp(), CReturn);
UASSERT_OBJ(returnp, m_scopetopp, "Lost CReturn in top change function");
// This is currently using AstLogOr which will shortcut the
// evaluation if any function returns true. This is likely what
// we want and is similar to the logic already in use inside
// V3EmitC, however, it also means that verbose logging may
// miss to print change detect variables.
AstNode* const newp = new AstCReturn{
m_scopetopp->fileline(),
new AstLogOr{m_scopetopp->fileline(), callp, returnp->lhsp()->unlinkFrBack()}};
returnp->replaceWith(newp);
VL_DO_DANGLING(returnp->deleteTree(), returnp);
}
m_numStmts = 0;
}
}
};
//######################################################################
// Utility visitor to find elements to be compared
class ChangedInsertVisitor final : public VNVisitor {
private:
// STATE
ChangedState& m_state; // Shared state across visitors
AstVarScope* const m_vscp; // Original (non-change) variable we're change-detecting
AstVarScope* m_newvscp = nullptr; // New (change detect) variable we're change-detecting
AstNode* m_varEqnp = nullptr; // Original var's equation to get var value
AstNode* m_newLvEqnp = nullptr; // New var's equation to read value
AstNode* m_newRvEqnp = nullptr; // New var's equation to set value
uint32_t m_detects = 0; // # detects created
// CONSTANTS
// How many indexes before error. Ok to increase this, but may result in much slower model
static constexpr uint32_t DETECTARRAY_MAX_INDEXES = 256;
void newChangeDet() {
if (++m_detects > DETECTARRAY_MAX_INDEXES) {
m_vscp->v3warn(E_DETECTARRAY,
"Unsupported: Can't detect more than "
<< DETECTARRAY_MAX_INDEXES
<< " array indexes (probably with UNOPTFLAT warning suppressed): "
<< m_vscp->prettyName() << '\n'
<< m_vscp->warnMore()
<< "... Could recompile with DETECTARRAY_MAX_INDEXES increased");
return;
}
m_state.maybeCreateChgFuncp();
AstChangeDet* const changep = new AstChangeDet{
m_vscp->fileline(), m_varEqnp->cloneTree(true), m_newRvEqnp->cloneTree(true)};
m_state.m_chgFuncp->addStmtsp(changep);
AstAssign* const initp = new AstAssign{m_vscp->fileline(), m_newLvEqnp->cloneTree(true),
m_varEqnp->cloneTree(true)};
m_state.m_chgFuncp->addFinalsp(initp);
// Later code will expand words which adds to GCC compile time,
// so add penalty based on word width also
m_state.m_numStmts += initp->nodeCount() + m_varEqnp->widthWords();
}
virtual void visit(AstBasicDType*) override { //
newChangeDet();
}
virtual void visit(AstPackArrayDType*) override { //
newChangeDet();
}
virtual void visit(AstUnpackArrayDType* nodep) override {
for (int index = 0; index < nodep->elementsConst(); ++index) {
VL_RESTORER(m_varEqnp);
VL_RESTORER(m_newLvEqnp);
VL_RESTORER(m_newRvEqnp);
{
m_varEqnp = new AstArraySel{nodep->fileline(), m_varEqnp->cloneTree(true), index};
m_newLvEqnp
= new AstArraySel{nodep->fileline(), m_newLvEqnp->cloneTree(true), index};
m_newRvEqnp
= new AstArraySel{nodep->fileline(), m_newRvEqnp->cloneTree(true), index};
iterate(nodep->subDTypep()->skipRefp());
m_varEqnp->deleteTree();
m_newLvEqnp->deleteTree();
m_newRvEqnp->deleteTree();
}
}
}
virtual void visit(AstNodeUOrStructDType* nodep) override {
if (nodep->packedUnsup()) {
newChangeDet();
} else {
if (debug()) nodep->dumpTree(cout, "-DETECTARRAY-class-");
m_vscp->v3warn(E_DETECTARRAY, "Unsupported: Can't detect changes on complex variable"
" (probably with UNOPTFLAT warning suppressed): "
<< m_vscp->varp()->prettyNameQ());
}
}
virtual void visit(AstNode* nodep) override {
iterateChildren(nodep);
if (debug()) nodep->dumpTree(cout, "-DETECTARRAY-general-");
m_vscp->v3warn(E_DETECTARRAY, "Unsupported: Can't detect changes on complex variable"
" (probably with UNOPTFLAT warning suppressed): "
<< m_vscp->varp()->prettyNameQ());
}
public:
// CONSTRUCTORS
ChangedInsertVisitor(AstVarScope* vscp, ChangedState& state)
: m_state{state}
, m_vscp{vscp} {
// DPI export trigger should never need change detect. See similar assertions in V3Order
// (OrderVisitor::nodeMarkCircular), and V3GenClk (GenClkRenameVisitor::genInpClk).
UASSERT_OBJ(vscp != v3Global.rootp()->dpiExportTriggerp(), vscp,
"DPI export trigger should not need change detect");
{
AstVar* const varp = m_vscp->varp();
const string newvarname{"__Vchglast__" + m_vscp->scopep()->nameDotless() + "__"
+ varp->shortName()};
// Create: VARREF(_last)
// ASSIGN(VARREF(_last), VARREF(var))
// ...
// CHANGEDET(VARREF(_last), VARREF(var))
AstVar* const newvarp
= new AstVar{varp->fileline(), VVarType::MODULETEMP, newvarname, varp};
m_state.m_topModp->addStmtp(newvarp);
m_newvscp = new AstVarScope{m_vscp->fileline(), m_state.m_scopetopp, newvarp};
m_state.m_scopetopp->addVarp(m_newvscp);
m_varEqnp = new AstVarRef{m_vscp->fileline(), m_vscp, VAccess::READ};
m_newLvEqnp = new AstVarRef{m_vscp->fileline(), m_newvscp, VAccess::WRITE};
m_newRvEqnp = new AstVarRef{m_vscp->fileline(), m_newvscp, VAccess::READ};
}
iterate(vscp->dtypep()->skipRefp());
m_varEqnp->deleteTree();
m_newLvEqnp->deleteTree();
m_newRvEqnp->deleteTree();
}
virtual ~ChangedInsertVisitor() override = default;
VL_UNCOPYABLE(ChangedInsertVisitor);
};
//######################################################################
// Changed class functions
void V3Changed::changedAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
ChangedState state;
state.m_scopetopp = nodep->topScopep()->scopep();
state.m_topModp = nodep->topModulep();
nodep->foreach<AstVarScope>([&state](AstVarScope* vscp) {
if (vscp->isCircular()) {
vscp->v3warn(IMPERFECTSCH,
"Imperfect scheduling of variable: " << vscp->prettyNameQ());
ChangedInsertVisitor{vscp, state};
}
});
V3Global::dumpCheckGlobalTree("changed", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}

View File

@ -1,32 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Pre C-Emit stage changes
//
// 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 VERILATOR_V3CHANGED_H_
#define VERILATOR_V3CHANGED_H_
#include "config_build.h"
#include "verilatedos.h"
class AstNetlist;
//============================================================================
class V3Changed final {
public:
static void changedAll(AstNetlist* nodep);
};
#endif // Guard

View File

@ -33,6 +33,7 @@
#include "V3Global.h"
#include "V3Clock.h"
#include "V3Ast.h"
#include "V3Sched.h"
#include <algorithm>
@ -69,116 +70,19 @@ public:
class ClockVisitor final : public VNVisitor {
private:
// NODE STATE
// Cleared each Module:
// AstVarScope::user1p() -> AstVarScope*. Temporary signal that was created.
const VNUser1InUse m_inuser1;
// STATE
AstNodeModule* m_modp = nullptr; // Current module
const AstTopScope* m_topScopep = nullptr; // Current top scope
AstScope* m_scopep = nullptr; // Current scope
AstCFunc* m_evalFuncp = nullptr; // Top eval function we are creating
AstCFunc* m_initFuncp = nullptr; // Top initial function we are creating
AstCFunc* m_finalFuncp = nullptr; // Top final function we are creating
AstCFunc* m_settleFuncp = nullptr; // Top settlement function we are creating
AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates.
AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under
AstMTaskBody* m_mtaskBodyp = nullptr; // Current mtask body
// METHODS
VL_DEBUG_FUNC; // Declare debug()
AstVarScope* getCreateLastClk(AstVarScope* vscp) {
if (vscp->user1p()) return static_cast<AstVarScope*>(vscp->user1p());
const AstVar* const varp = vscp->varp();
if (!varp->width1()) {
varp->v3warn(E_UNSUPPORTED, "Unsupported: Clock edge on non-single bit signal: "
<< varp->prettyNameQ());
}
const string newvarname
= (string("__Vclklast__") + vscp->scopep()->nameDotless() + "__" + varp->name());
AstVar* const newvarp = new AstVar(vscp->fileline(), VVarType::MODULETEMP, newvarname,
VFlagLogicPacked(), 1);
newvarp->noReset(true); // Reset by below assign
m_modp->addStmtp(newvarp);
AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopep, newvarp);
vscp->user1p(newvscp);
m_scopep->addVarp(newvscp);
// Add init
AstNode* fromp = new AstVarRef(newvarp->fileline(), vscp, VAccess::READ);
if (v3Global.opt.xInitialEdge()) fromp = new AstNot(fromp->fileline(), fromp);
AstNode* const newinitp = new AstAssign(
vscp->fileline(), new AstVarRef(newvarp->fileline(), newvscp, VAccess::WRITE), fromp);
addToInitial(newinitp);
// At bottom, assign them
AstAssign* const finalp = new AstAssign(
vscp->fileline(), new AstVarRef(vscp->fileline(), newvscp, VAccess::WRITE),
new AstVarRef(vscp->fileline(), vscp, VAccess::READ));
m_evalFuncp->addFinalsp(finalp);
//
UINFO(4, "New Last: " << newvscp << endl);
return newvscp;
}
AstNode* createSenItemEquation(AstSenItem* nodep) {
// We know the var is clean, and one bit, so we use binary ops
// for speed instead of logical ops.
// POSEDGE: var & ~var_last
// NEGEDGE: ~var & var_last
// BOTHEDGE: var ^ var_last
// HIGHEDGE: var
// LOWEDGE: ~var
// ANYEDGE: var ^ var_last
AstNode* newp = nullptr;
if (nodep->edgeType() == VEdgeType::ET_ILLEGAL) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Complicated event expression in sensitive activity list");
return nullptr;
}
UASSERT_OBJ(nodep->varrefp(), nodep, "No clock found on sense item");
AstVarScope* const clkvscp = nodep->varrefp()->varScopep();
if (nodep->edgeType() == VEdgeType::ET_POSEDGE) {
AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
newp = new AstAnd(
nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ),
new AstNot(nodep->fileline(),
new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ)));
} else if (nodep->edgeType() == VEdgeType::ET_NEGEDGE) {
AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
newp = new AstAnd(
nodep->fileline(),
new AstNot(nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(),
VAccess::READ)),
new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ));
} else if (nodep->edgeType() == VEdgeType::ET_BOTHEDGE) {
AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
newp = new AstXor(
nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ),
new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ));
} else if (nodep->edgeType() == VEdgeType::ET_HIGHEDGE) {
newp = new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ);
} else if (nodep->edgeType() == VEdgeType::ET_LOWEDGE) {
newp = new AstNot(nodep->fileline(),
new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ));
} else {
nodep->v3fatalSrc("Bad edge type");
}
return newp;
}
AstNode* createSenseEquation(AstSenItem* nodesp) {
// Nodep may be a list of elements; we need to walk it
AstNode* senEqnp = nullptr;
for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) {
AstNode* const senOnep = createSenItemEquation(senp);
if (senEqnp) {
// Add new OR to the sensitivity list equation
senEqnp = new AstOr(senp->fileline(), senEqnp, senOnep);
} else {
senEqnp = senOnep;
}
UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered");
AstNode* const senOnep = senp->sensp()->cloneTree(false);
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
}
return senEqnp;
}
@ -192,116 +96,7 @@ private:
m_lastSenp = nullptr;
m_lastIfp = nullptr;
}
AstCFunc* makeTopFunction(const string& name, bool slow = false) {
AstCFunc* const funcp = new AstCFunc{m_topScopep->fileline(), name, m_topScopep->scopep()};
funcp->dontCombine(true);
funcp->isStatic(false);
funcp->isLoose(true);
funcp->entryPoint(true);
funcp->slow(slow);
funcp->isConst(false);
funcp->declPrivate(true);
m_topScopep->scopep()->addActivep(funcp);
return funcp;
}
void splitCheck(AstCFunc* ofuncp) {
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
int funcnum = 0;
int func_stmts = 0;
AstCFunc* funcp = nullptr;
// Unlink all statements, then add item by item to new sub-functions
AstBegin* const tempp = new AstBegin{ofuncp->fileline(), "[EditWrapper]",
ofuncp->stmtsp()->unlinkFrBackWithNext()};
if (ofuncp->finalsp()) tempp->addStmtsp(ofuncp->finalsp()->unlinkFrBackWithNext());
while (tempp->stmtsp()) {
AstNode* const itemp = tempp->stmtsp()->unlinkFrBack();
const int stmts = itemp->nodeCount();
if (!funcp || (func_stmts + stmts) > v3Global.opt.outputSplitCFuncs()) {
// Make a new function
funcp
= new AstCFunc{ofuncp->fileline(), ofuncp->name() + "__" + cvtToStr(funcnum++),
m_topScopep->scopep()};
funcp->dontCombine(true);
funcp->isStatic(false);
funcp->isLoose(true);
funcp->slow(ofuncp->slow());
m_topScopep->scopep()->addActivep(funcp);
//
AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
ofuncp->addStmtsp(callp);
func_stmts = 0;
}
funcp->addStmtsp(itemp);
func_stmts += stmts;
}
VL_DO_DANGLING(tempp->deleteTree(), tempp);
}
// VISITORS
virtual void visit(AstTopScope* nodep) override {
UINFO(4, " TOPSCOPE " << nodep << endl);
m_topScopep = nodep;
m_scopep = nodep->scopep();
UASSERT_OBJ(m_scopep, nodep,
"No scope found on top level, perhaps you have no statements?");
// VV***** We reset all user1p()
AstNode::user1ClearTree();
// Make top functions
m_evalFuncp = makeTopFunction("_eval");
m_initFuncp = makeTopFunction("_eval_initial", /* slow: */ true);
m_settleFuncp = makeTopFunction("_eval_settle", /* slow: */ true);
m_finalFuncp = makeTopFunction("_final", /* slow: */ true);
// Process the activates
iterateChildren(nodep);
UINFO(4, " TOPSCOPE iter done " << nodep << endl);
// Clear the DPI export trigger flag at the end of eval
if (AstVarScope* const dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp()) {
FileLine* const fl = dpiExportTriggerp->fileline();
AstAssign* const assignp
= new AstAssign{fl, new AstVarRef{fl, dpiExportTriggerp, VAccess::WRITE},
new AstConst{fl, AstConst::BitFalse{}}};
m_evalFuncp->addFinalsp(assignp);
}
// Split large functions
splitCheck(m_evalFuncp);
splitCheck(m_initFuncp);
splitCheck(m_finalFuncp);
splitCheck(m_settleFuncp);
// Done, clear so we can detect errors
UINFO(4, " TOPSCOPEDONE " << nodep << endl);
clearLastSen();
m_topScopep = nullptr;
m_scopep = nullptr;
}
virtual void visit(AstNodeModule* nodep) override {
// UINFO(4, " MOD " << nodep << endl);
VL_RESTORER(m_modp);
{
m_modp = nodep;
iterateChildren(nodep);
}
}
virtual void visit(AstScope* nodep) override {
// UINFO(4, " SCOPE " << nodep << endl);
m_scopep = nodep;
iterateChildren(nodep);
if (AstNode* const movep = nodep->finalClksp()) {
UASSERT_OBJ(m_topScopep, nodep, "Final clocks under non-top scope");
movep->unlinkFrBackWithNext();
m_evalFuncp->addFinalsp(movep);
}
m_scopep = nullptr;
}
virtual void visit(AstNodeProcedure* nodep) override {
if (AstNode* const stmtsp = nodep->bodysp()) {
stmtsp->unlinkFrBackWithNext();
nodep->addNextHere(stmtsp);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
virtual void visit(AstCoverToggle* nodep) override {
// nodep->dumpTree(cout, "ct:");
// COVERTOGGLE(INC, ORIG, CHANGE) ->
@ -319,110 +114,44 @@ private:
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstCFunc* nodep) override {
iterateChildren(nodep);
// Link to global function
if (nodep->isFinal()) {
UINFO(4, " isFinal " << nodep << endl);
AstCCall* const callp = new AstCCall(nodep->fileline(), nodep);
m_finalFuncp->addStmtsp(callp);
}
}
virtual void visit(AstSenTree* nodep) override {
// Delete it later; Actives still pointing to it
nodep->unlinkFrBack();
pushDeletep(nodep);
}
void addToEvalLoop(AstNode* stmtsp) {
m_evalFuncp->addStmtsp(stmtsp); // add to top level function
}
void addToSettleLoop(AstNode* stmtsp) {
m_settleFuncp->addStmtsp(stmtsp); // add to top level function
}
void addToInitial(AstNode* stmtsp) {
m_initFuncp->addStmtsp(stmtsp); // add to top level function
pushDeletep(nodep); // Delete it later, AstActives still pointing to it
}
virtual void visit(AstActive* nodep) override {
// Careful if adding variables here, ACTIVES can be under other ACTIVES
// Need to save and restore any member state in AstUntilStable block
if (!m_topScopep || !nodep->stmtsp()) {
// Not at the top or empty block...
// Only empty blocks should be leftover on the non-top. Killem.
UASSERT_OBJ(!nodep->stmtsp(), nodep, "Non-empty lower active");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (m_mtaskBodyp) {
UINFO(4, " TR ACTIVE " << nodep << endl);
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
if (nodep->hasClocked()) {
UASSERT_OBJ(!nodep->hasInitial(), nodep,
"Initial block should not have clock sensitivity");
if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) {
UINFO(4, " sameSenseTree\n");
} else {
clearLastSen();
m_lastSenp = nodep->sensesp();
// Make a new if statement
m_lastIfp = makeActiveIf(m_lastSenp);
m_mtaskBodyp->addStmtsp(m_lastIfp);
}
// Move statements to if
m_lastIfp->addIfsp(stmtsp);
} else if (nodep->hasInitial() || nodep->hasSettle()) {
nodep->v3fatalSrc("MTask should not include initial/settle logic.");
} else {
// Combo logic. Move statements to mtask func.
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty");
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
if (nodep->hasClocked()) {
// Create 'if' statement, if needed
if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) {
clearLastSen();
m_mtaskBodyp->addStmtsp(stmtsp);
m_lastSenp = nodep->sensesp();
// Make a new if statement
m_lastIfp = makeActiveIf(m_lastSenp);
relinker.relink(m_lastIfp);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
// Move statements to if
m_lastIfp->addIfsp(stmtsp);
} else if (nodep->hasCombo()) {
clearLastSen();
// Move statements to body
relinker.relink(stmtsp);
} else {
UINFO(4, " ACTIVE " << nodep << endl);
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
if (nodep->hasClocked()) {
// Remember the latest sensitivity so we can compare it next time
UASSERT_OBJ(!nodep->hasInitial(), nodep,
"Initial block should not have clock sensitivity");
if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) {
UINFO(4, " sameSenseTree\n");
} else {
clearLastSen();
m_lastSenp = nodep->sensesp();
// Make a new if statement
m_lastIfp = makeActiveIf(m_lastSenp);
addToEvalLoop(m_lastIfp);
}
// Move statements to if
m_lastIfp->addIfsp(stmtsp);
} else if (nodep->hasInitial()) {
// Don't need to: clearLastSen();, as we're adding it to different cfunc
// Move statements to function
addToInitial(stmtsp);
} else if (nodep->hasSettle()) {
// Don't need to: clearLastSen();, as we're adding it to different cfunc
// Move statements to function
addToSettleLoop(stmtsp);
} else {
// Combo
clearLastSen();
// Move statements to function
addToEvalLoop(stmtsp);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
nodep->v3fatalSrc("Should have been removed by V3Sched::schedule");
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstExecGraph* nodep) override {
VL_RESTORER(m_mtaskBodyp);
for (m_mtaskBodyp = nodep->mTaskBodiesp(); m_mtaskBodyp;
m_mtaskBodyp = VN_AS(m_mtaskBodyp->nextp(), MTaskBody)) {
for (AstMTaskBody* mtaskBodyp = nodep->mTaskBodiesp(); mtaskBodyp;
mtaskBodyp = VN_AS(mtaskBodyp->nextp(), MTaskBody)) {
clearLastSen();
iterate(m_mtaskBodyp);
iterate(mtaskBodyp);
}
clearLastSen();
// Move the ExecGraph into _eval. Its location marks the
// spot where the graph will execute, relative to other
// (serial) logic in the cycle.
nodep->unlinkFrBack();
addToEvalLoop(nodep);
}
//--------------------
@ -430,12 +159,7 @@ private:
public:
// CONSTRUCTORS
explicit ClockVisitor(AstNetlist* nodep) {
iterate(nodep);
// Allow downstream modules to find _eval()
// easily without iterating through the tree.
nodep->evalp(m_evalFuncp);
}
explicit ClockVisitor(AstNetlist* netlistp) { iterate(netlistp); }
virtual ~ClockVisitor() override = default;
};

View File

@ -2595,7 +2595,14 @@ private:
// Constants in sensitivity lists may be removed (we'll simplify later)
if (nodep->isClocked()) { // A constant can never get a pos/negedge
if (onlySenItemInSenTree(nodep)) {
nodep->replaceWith(new AstSenItem(nodep->fileline(), AstSenItem::Never()));
if (nodep->edgeType() == VEdgeType::ET_CHANGED) {
// TODO: This really is dodgy, as strictgly compliant simulators will not
// execute this block, but but t_func_check relies on it
nodep->replaceWith(
new AstSenItem(nodep->fileline(), AstSenItem::Initial()));
} else {
nodep->replaceWith(new AstSenItem(nodep->fileline(), AstSenItem::Never()));
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
} else {
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
@ -2619,12 +2626,6 @@ private:
UASSERT_OBJ(senvarp, sensp, "Non-varref sensitivity variable");
sensp->replaceWith(senvarp);
VL_DO_DANGLING(sensp->deleteTree(), sensp);
} else if (!m_doNConst // Deal with later when doNConst missing
&& (VN_IS(nodep->sensp(), EnumItemRef) || VN_IS(nodep->sensp(), Const))) {
} else if (nodep->isIllegal()) { // Deal with later
} else {
UASSERT_OBJ(!(nodep->hasVar() && !nodep->varrefp()), nodep,
"Null sensitivity variable");
}
}
@ -2633,8 +2634,10 @@ private:
if (lhsp->type() < rhsp->type()) return true;
if (lhsp->type() > rhsp->type()) return false;
// Looks visually better if we keep sorted by name
if (!lhsp->varrefp() && rhsp->varrefp()) return true;
if (lhsp->varrefp() && !rhsp->varrefp()) return false;
if (!lhsp->sensp() && rhsp->sensp()) return true;
if (lhsp->sensp() && !rhsp->sensp()) return false;
if (lhsp->varrefp() && !rhsp->varrefp()) return true;
if (!lhsp->varrefp() && rhsp->varrefp()) return false;
if (lhsp->varrefp() && rhsp->varrefp()) {
if (lhsp->varrefp()->name() < rhsp->varrefp()->name()) return true;
if (lhsp->varrefp()->name() > rhsp->varrefp()->name()) return false;
@ -2644,6 +2647,27 @@ private:
// Or rarely, different data types
if (lhsp->varrefp()->dtypep() < rhsp->varrefp()->dtypep()) return true;
if (lhsp->varrefp()->dtypep() > rhsp->varrefp()->dtypep()) return false;
} else if (AstCMethodHard* const lp = VN_CAST(lhsp->sensp(), CMethodHard)) {
if (AstCMethodHard* const rp = VN_CAST(rhsp->sensp(), CMethodHard)) {
if (AstVarRef* const lRefp = VN_CAST(lp->fromp(), VarRef)) {
if (AstVarRef* const rRefp = VN_CAST(rp->fromp(), VarRef)) {
if (lRefp->name() < rRefp->name()) return true;
if (lRefp->name() > rRefp->name()) return false;
// But might be same name with different scopes
if (lRefp->varScopep() < rRefp->varScopep()) return true;
if (lRefp->varScopep() > rRefp->varScopep()) return false;
// Or rarely, different data types
if (lRefp->dtypep() < rRefp->dtypep()) return true;
if (lRefp->dtypep() > rRefp->dtypep()) return false;
}
}
if (AstConst* lConstp = VN_CAST(lp->pinsp(), Const)) {
if (AstConst* rConstp = VN_CAST(rp->pinsp(), Const)) {
if (lConstp->toUInt() < rConstp->toUInt()) return true;
if (lConstp->toUInt() > rConstp->toUInt()) return false;
}
}
}
}
// Sort by edge, AFTER variable, as we want multiple edges for same var adjacent.
// note the SenTree optimizer requires this order (more
@ -2706,17 +2730,13 @@ private:
AstSenItem* const litemp = senp;
AstSenItem* const ritemp = nextp;
if (ritemp) {
if ((litemp->varrefp() && ritemp->varrefp()
&& litemp->varrefp()->sameGateTree(ritemp->varrefp()))
|| (!litemp->varrefp() && !ritemp->varrefp())) {
if ((litemp->sensp() && ritemp->sensp()
&& litemp->sensp()->sameGateTree(ritemp->sensp()))
|| (!litemp->sensp() && !ritemp->sensp())) {
// We've sorted in the order ANY, BOTH, POS, NEG,
// so we don't need to try opposite orders
if ((litemp->edgeType()
== VEdgeType::ET_ANYEDGE) // ANY or {BOTH|POS|NEG} -> ANY
|| (litemp->edgeType()
== VEdgeType::ET_BOTHEDGE) // BOTH or {POS|NEG} -> BOTH
|| (litemp->edgeType() == VEdgeType::ET_POSEDGE // POS or NEG -> BOTH
&& ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
if ((litemp->edgeType() == VEdgeType::ET_POSEDGE // POS or NEG -> BOTH
&& ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
|| (litemp->edgeType() == ritemp->edgeType()) // Identical edges
) {
// Fix edge of old node

View File

@ -131,7 +131,7 @@ private:
// Class packages might have no children, but need to remain as
// long as the class they refer to is needed
if (VN_IS(m_modp, Class) || VN_IS(m_modp, ClassPackage)) nodep->user1Inc();
if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp() && !nodep->finalClksp()) {
if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp()) {
m_scopesp.push_back(nodep);
}
}

View File

@ -93,7 +93,7 @@ private:
AstAssignDly* m_nextDlyp = nullptr; // Next delayed assignment in a list of assignments
bool m_inDly = false; // True in delayed assignments
bool m_inLoop = false; // True in for loops
bool m_inInitial = false; // True in initial blocks
bool m_inInitial = false; // True in static initializers and initial blocks
using VarMap = std::map<const std::pair<AstNodeModule*, std::string>, AstVar*>;
VarMap m_modVarMap; // Table of new var names created under module
VDouble0 m_statSharedSet; // Statistic tracking
@ -160,7 +160,7 @@ private:
return varscp;
}
AstActive* createActivePost(AstVarRef* varrefp) {
AstActive* createActive(AstNode* varrefp) {
AstActive* const newactp
= new AstActive(varrefp->fileline(), "sequentdly", m_activep->sensesp());
// Was addNext(), but addNextHere() avoids a linear search.
@ -336,7 +336,7 @@ private:
} 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 = createActivePost(varrefp);
AstActive* const newactp = createActive(varrefp);
newactp->addStmtsp(finalp);
varrefp->varScopep()->user4p(finalp);
finalp->user2p(newactp);
@ -383,12 +383,54 @@ private:
m_activep = nodep;
VL_RESTORER(m_inInitial);
{
m_inInitial = nodep->hasInitial();
AstSenTree* const senTreep = nodep->sensesp();
m_inInitial = senTreep->hasStatic() || senTreep->hasInitial();
// Two sets to same variable in different actives must use different vars.
AstNode::user3ClearTree();
iterateChildren(nodep);
}
}
virtual void visit(AstFireEvent* nodep) override {
UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent");
FileLine* const flp = nodep->fileline();
if (nodep->isDeleyed()) {
AstVarRef* const vrefp = VN_AS(nodep->operandp(), VarRef);
vrefp->unlinkFrBack();
const string newvarname = (string("__Vdly__") + vrefp->varp()->shortName());
AstVarScope* const dlyvscp = createVarSc(vrefp->varScopep(), newvarname, 1, nullptr);
const auto dlyRef = [=](VAccess access) {
return new AstVarRef{flp, dlyvscp, access};
};
AstAssignPre* const prep = new AstAssignPre{flp, dlyRef(VAccess::WRITE),
new AstConst{flp, AstConst::BitFalse{}}};
AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr};
{
AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
postp->addStmtp(ifp);
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "fire"};
callp->statement(true);
callp->dtypeSetVoid();
ifp->addIfsp(callp);
}
AstActive* const activep = createActive(nodep);
activep->addStmtsp(prep);
activep->addStmtsp(postp);
AstAssign* const assignp = new AstAssign{flp, dlyRef(VAccess::WRITE),
new AstConst{flp, AstConst::BitTrue{}}};
nodep->replaceWith(assignp);
} else {
AstCMethodHard* const callp
= new AstCMethodHard{flp, nodep->operandp()->unlinkFrBack(), "fire"};
callp->dtypeSetVoid();
callp->statement(true);
nodep->replaceWith(callp);
}
nodep->deleteTree();
}
virtual void visit(AstAssignDly* nodep) override {
m_inDly = true;
m_nextDlyp
@ -407,7 +449,7 @@ private:
"loops (non-delayed is ok - see docs)");
}
const AstBasicDType* const basicp = lhsp->dtypep()->basicp();
if (basicp && basicp->isEventValue()) {
if (basicp && basicp->isEvent()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays");
}
if (newlhsp) {
@ -444,20 +486,10 @@ private:
if (!dlyvscp) { // First use of this delayed variable
const string newvarname = (string("__Vdly__") + nodep->varp()->shortName());
dlyvscp = createVarSc(oldvscp, newvarname, 0, nullptr);
AstNodeAssign* prep;
const AstBasicDType* const basicp = oldvscp->dtypep()->basicp();
if (basicp && basicp->isEventValue()) {
// Events go to zero on next timestep unless reactivated
prep = new AstAssignPre(
nodep->fileline(),
new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE),
new AstConst(nodep->fileline(), AstConst::BitFalse()));
} else {
prep = new AstAssignPre(
nodep->fileline(),
new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE),
new AstVarRef(nodep->fileline(), oldvscp, VAccess::READ));
}
AstNodeAssign* const prep = new AstAssignPre(
nodep->fileline(),
new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE),
new AstVarRef(nodep->fileline(), oldvscp, VAccess::READ));
AstNodeAssign* const postp = new AstAssignPost(
nodep->fileline(),
new AstVarRef(nodep->fileline(), oldvscp, VAccess::WRITE),
@ -465,7 +497,7 @@ private:
postp->lhsp()->user2(true); // Don't detect this assignment
oldvscp->user1p(dlyvscp); // So we can find it later
// Make new ACTIVE with identical sensitivity tree
AstActive* const newactp = createActivePost(nodep);
AstActive* const newactp = createActive(nodep);
dlyvscp->user2p(newactp);
newactp->addStmtsp(prep); // Add to FRONT of statements
newactp->addStmtsp(postp);

View File

@ -731,80 +731,3 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
}
return "";
}
void EmitCFunc::doubleOrDetect(AstChangeDet* changep, bool& gotOne) {
// cppcheck-suppress variableScope
static int s_addDoubleOr = 10; // Determined experimentally as best
if (!changep->rhsp()) {
if (!gotOne) {
gotOne = true;
} else {
puts(" | ");
}
iterateAndNextNull(changep->lhsp());
} else {
AstNode* const lhsp = changep->lhsp();
AstNode* const rhsp = changep->rhsp();
UASSERT_OBJ(VN_IS(lhsp, VarRef) || VN_IS(lhsp, ArraySel), changep, "Not ref?");
UASSERT_OBJ(VN_IS(rhsp, VarRef) || VN_IS(rhsp, ArraySel), changep, "Not ref?");
for (int word = 0; word < (changep->lhsp()->isWide() ? changep->lhsp()->widthWords() : 1);
++word) {
if (!gotOne) {
gotOne = true;
s_addDoubleOr = 10;
puts("(");
} else if (--s_addDoubleOr == 0) {
puts("|| (");
s_addDoubleOr = 10;
} else {
puts(" | (");
}
iterateAndNextNull(changep->lhsp());
if (changep->lhsp()->isWide()) puts("[" + cvtToStr(word) + "]");
if (changep->lhsp()->isDouble()) {
puts(" != ");
} else {
puts(" ^ ");
}
iterateAndNextNull(changep->rhsp());
if (changep->lhsp()->isWide()) puts("[" + cvtToStr(word) + "]");
puts(")");
}
}
}
void EmitCFunc::emitChangeDet() {
putsDecoration("// Change detection\n");
puts("QData __req = false; // Logically a bool\n"); // But not because it results in
// faster code
bool gotOne = false;
for (AstChangeDet* const changep : m_blkChangeDetVec) {
if (changep->lhsp()) {
if (!gotOne) { // Not a clocked block
puts("__req |= (");
} else {
puts("\n");
}
doubleOrDetect(changep, gotOne);
}
}
if (gotOne) puts(");\n");
if (gotOne && !v3Global.opt.protectIds()) {
// puts("VL_DEBUG_IF( if (__req) cout<<\"- CLOCKREQ );");
for (AstChangeDet* nodep : m_blkChangeDetVec) {
if (nodep->lhsp()) {
puts("VL_DEBUG_IF( if(__req && (");
bool gotOneIgnore = false;
doubleOrDetect(nodep, gotOneIgnore);
string varname;
if (VN_IS(nodep->lhsp(), VarRef)) {
varname = ": " + VN_AS(nodep->lhsp(), VarRef)->varp()->prettyName();
}
puts(")) VL_DBG_MSGF(\" CHANGE: ");
puts(protect(nodep->fileline()->filename()));
puts(":" + cvtToStr(nodep->fileline()->lineno()));
puts(varname + "\\n\"); );\n");
}
}
}
}

View File

@ -119,7 +119,6 @@ private:
int m_labelNum = 0; // Next label number
int m_splitSize = 0; // # of cfunc nodes placed into output file
bool m_inUC = false; // Inside an AstUCStmt or AstUCMath
std::vector<AstChangeDet*> m_blkChangeDetVec; // All encountered changes in block
bool m_emitConstInit = false; // Emitting constant initializer
protected:
@ -177,7 +176,6 @@ public:
void emitVarReset(AstVar* varp);
string emitVarResetRecurse(const AstVar* varp, const string& varNameProtected,
AstNodeDType* dtypep, int depth, const string& suffix);
void doubleOrDetect(AstChangeDet* changep, bool& gotOne);
void emitChangeDet();
void emitConstInit(AstNode* initp) {
// We should refactor emit to produce output into a provided buffer, not go through members
@ -194,8 +192,6 @@ public:
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
m_blkChangeDetVec.clear();
splitSizeInc(nodep);
puts("\n");
@ -244,15 +240,11 @@ public:
iterateAndNextNull(nodep->stmtsp());
}
if (!m_blkChangeDetVec.empty()) emitChangeDet();
if (nodep->finalsp()) {
putsDecoration("// Final\n");
iterateAndNextNull(nodep->finalsp());
}
if (!m_blkChangeDetVec.empty()) puts("return __req;\n");
puts("}\n");
if (nodep->ifdef() != "") puts("#endif // " + nodep->ifdef() + "\n");
}
@ -1207,9 +1199,6 @@ public:
UASSERT_OBJ(!nodep->mTaskBodiesp(), nodep, "These should have been lowered");
iterateChildrenConst(nodep);
}
virtual void visit(AstChangeDet* nodep) override { //
m_blkChangeDetVec.push_back(nodep);
}
// Default
virtual void visit(AstNode* nodep) override {

View File

@ -372,6 +372,7 @@ class EmitCImp final : EmitCFunc {
// lower level subinst code does it.
} else if (varp->isParam()) {
} else if (varp->isStatic() && varp->isConst()) {
} else if (varp->basicp() && varp->basicp()->isTriggerVec()) {
} else {
int vects = 0;
AstNodeDType* elementp = varp->dtypeSkipRefp();

View File

@ -314,97 +314,25 @@ class EmitCModel final : public EmitCFunc {
puts("}\n");
}
void emitSettleLoop(AstNodeModule* modp, bool initial) {
const string topModNameProtected = prefixNameProtect(modp);
putsDecoration("// Evaluate till stable\n");
if (v3Global.rootp()->changeRequest()) {
puts("int __VclockLoop = 0;\n");
puts("QData __Vchange = 1;\n");
}
if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n");
puts("do {\n");
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ ");
puts(initial ? "Initial" : "Clock");
puts(" loop\\n\"););\n");
if (initial)
puts(topModNameProtected + "__" + protect("_eval_settle") + "(&(vlSymsp->TOP));\n");
if (v3Global.opt.profExec() && !initial) {
puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalLoopBegin();\n");
}
puts(topModNameProtected + "__" + protect("_eval") + "(&(vlSymsp->TOP));\n");
if (v3Global.opt.profExec() && !initial) {
puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalLoopEnd();\n");
}
if (v3Global.rootp()->changeRequest()) {
puts("if (VL_UNLIKELY(++__VclockLoop > " + cvtToStr(v3Global.opt.convergeLimit())
+ ")) {\n");
puts("// About to fail, so enable debug to see what's not settling.\n");
puts("// Note you must run make with OPT=-DVL_DEBUG for debug prints.\n");
puts("int __Vsaved_debug = Verilated::debug();\n");
puts("Verilated::debug(1);\n");
puts("__Vchange = " + topModNameProtected + "__" + protect("_change_request")
+ "(&(vlSymsp->TOP));\n");
puts("Verilated::debug(__Vsaved_debug);\n");
puts("VL_FATAL_MT(");
putsQuoted(protect(modp->fileline()->filename()));
puts(", ");
puts(cvtToStr(modp->fileline()->lineno()));
puts(", \"\",\n");
puts("\"Verilated model didn't ");
if (initial) puts("DC ");
puts("converge\\n\"\n");
puts("\"- See https://verilator.org/warn/DIDNOTCONVERGE\");\n");
puts("} else {\n");
puts("__Vchange = " + topModNameProtected + "__" + protect("_change_request")
+ "(&(vlSymsp->TOP));\n");
puts("}\n");
}
puts("} while ("
+ (v3Global.rootp()->changeRequest() ? std::string{"VL_UNLIKELY(__Vchange)"}
: std::string{"0"})
+ ");\n");
}
void emitStandardMethods1(AstNodeModule* modp) {
UASSERT_OBJ(modp->isTop(), modp, "Attempting to emitWrapEval for non-top class");
const string topModNameProtected = prefixNameProtect(modp);
const string selfDecl = "(" + topModNameProtected + "* vlSelf)";
putSectionDelimiter("Evaluation loop");
putSectionDelimiter("Evaluation function");
// Forward declarations
puts("\n");
puts("void " + topModNameProtected + "__" + protect("_eval_initial") + selfDecl + ";\n");
puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n");
puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n");
if (v3Global.rootp()->changeRequest()) {
puts("QData " + topModNameProtected + "__" + protect("_change_request") + selfDecl
+ ";\n");
}
puts("#ifdef VL_DEBUG\n");
puts("void " + topModNameProtected + "__" + protect("_eval_debug_assertions") + selfDecl
+ ";\n");
puts("#endif // VL_DEBUG\n");
puts("void " + topModNameProtected + "__" + protect("_final") + selfDecl + ";\n");
puts("void " + topModNameProtected + "__" + protect("_eval_static") + selfDecl + ";\n");
puts("void " + topModNameProtected + "__" + protect("_eval_initial") + selfDecl + ";\n");
puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n");
puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n");
// _eval_initial_loop
puts("\nstatic void " + protect("_eval_initial_loop") + "(" + symClassVar() + ")"
+ " {\n");
puts("vlSymsp->__Vm_didInit = true;\n");
puts(topModNameProtected + "__" + protect("_eval_initial") + "(&(vlSymsp->TOP));\n");
emitSettleLoop(modp, /* initial: */ true);
ensureNewLine();
puts("}\n");
}
void emitStandardMethods2(AstNodeModule* modp) {
const string topModNameProtected = prefixNameProtect(modp);
// ::eval_step
puts("\nvoid " + topClassName() + "::eval_step() {\n");
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName()
@ -416,9 +344,17 @@ class EmitCModel final : public EmitCFunc {
+ "(&(vlSymsp->TOP));\n");
puts("#endif // VL_DEBUG\n");
putsDecoration("// Initialize\n");
puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) " + protect("_eval_initial_loop")
+ "(vlSymsp);\n");
if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n");
if (v3Global.hasEvents()) puts("vlSymsp->clearTriggeredEvents();\n");
puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) {\n");
puts("vlSymsp->__Vm_didInit = true;\n");
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ Initial\\n\"););\n");
puts(topModNameProtected + "__" + protect("_eval_static") + "(&(vlSymsp->TOP));\n");
puts(topModNameProtected + "__" + protect("_eval_initial") + "(&(vlSymsp->TOP));\n");
puts(topModNameProtected + "__" + protect("_eval_settle") + "(&(vlSymsp->TOP));\n");
puts("}\n");
if (v3Global.opt.threads() == 1) {
const uint32_t mtaskId = 0;
@ -432,7 +368,8 @@ class EmitCModel final : public EmitCFunc {
puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalBegin();\n");
}
emitSettleLoop(modp, /* initial: */ false);
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ Eval\\n\"););\n");
puts(topModNameProtected + "__" + protect("_eval") + "(&(vlSymsp->TOP));\n");
putsDecoration("// Evaluate cleanup\n");
if (v3Global.opt.threads() == 1) {
@ -444,8 +381,10 @@ class EmitCModel final : public EmitCFunc {
puts("}\n");
}
void emitStandardMethods3(AstNodeModule* modp) {
void emitStandardMethods2(AstNodeModule* modp) {
const string topModNameProtected = prefixNameProtect(modp);
const string selfDecl = "(" + topModNameProtected + "* vlSelf)";
// ::eval_end_step
if (v3Global.needTraceDumper() && !optSystemC()) {
puts("\nvoid " + topClassName() + "::eval_end_step() {\n");
@ -473,9 +412,12 @@ class EmitCModel final : public EmitCFunc {
}
putSectionDelimiter("Invoke final blocks");
// Forward declarations
puts("\n");
puts("void " + topModNameProtected + "__" + protect("_eval_final") + selfDecl + ";\n");
// ::final
puts("\nVL_ATTR_COLD void " + topClassName() + "::final() {\n");
puts(/**/ topModNameProtected + "__" + protect("_final") + "(&(vlSymsp->TOP));\n");
puts(/**/ topModNameProtected + "__" + protect("_eval_final") + "(&(vlSymsp->TOP));\n");
puts("}\n");
}
@ -599,7 +541,6 @@ class EmitCModel final : public EmitCFunc {
emitDestructorImplementation();
emitStandardMethods1(modp);
emitStandardMethods2(modp);
emitStandardMethods3(modp);
if (v3Global.opt.trace()) { emitTraceMethods(modp); }
if (v3Global.opt.savable()) { emitSerializationFunctions(); }

View File

@ -443,6 +443,7 @@ void EmitCSyms::emitSymHdr() {
puts("uint32_t __Vm_baseCode = 0;"
" ///< Used by trace routines when tracing multiple models\n");
}
if (v3Global.hasEvents()) puts("std::vector<VlEvent*> __Vm_triggeredEvents;\n");
puts("bool __Vm_didInit = false;\n");
if (v3Global.opt.profExec()) {
@ -506,6 +507,22 @@ void EmitCSyms::emitSymHdr() {
puts("\n// METHODS\n");
puts("const char* name() { return TOP.name(); }\n");
if (v3Global.hasEvents()) {
puts("void enqueueTriggeredEventForClearing(VlEvent& event) {\n");
puts("#ifdef VL_DEBUG\n");
puts("if (VL_UNLIKELY(!event.isTriggered())) {\n");
puts("VL_FATAL_MT(__FILE__, __LINE__, __FILE__, \"event passed to "
"'enqueueTriggeredEventForClearing' was not triggered\");\n");
puts("}\n");
puts("#endif\n");
puts("__Vm_triggeredEvents.push_back(&event);\n");
puts("}\n");
puts("void clearTriggeredEvents() {\n");
puts("for (const auto eventp : __Vm_triggeredEvents) eventp->clearTriggered();\n");
puts("__Vm_triggeredEvents.clear();\n");
puts("}\n");
}
if (v3Global.needTraceDumper()) {
if (!optSystemC()) puts("void _traceDump();\n");
puts("void _traceDumpOpen();\n");

View File

@ -421,6 +421,16 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor {
puts(")");
}
virtual void visit(AstCMethodHard* nodep) override {
iterate(nodep->fromp());
puts("." + nodep->name() + "(");
for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) {
if (pinp != nodep->pinsp()) puts(", ");
iterate(pinp);
}
puts(")");
}
// Operators
virtual void emitVerilogFormat(AstNode* nodep, const string& format, AstNode* lhsp = nullptr,
AstNode* const rhsp = nullptr, AstNode* thsp = nullptr,

View File

@ -206,7 +206,6 @@ class EmitXmlFileVisitor final : public VNVisitor {
} else if (nodep->attrClocker() == VVarAttrClocker::CLOCKER_NO) {
puts(" clocker=\"false\"");
}
if (nodep->attrClockEn()) puts(" clock_enable=\"true\"");
if (nodep->attrIsolateAssign()) puts(" isolate_assignments=\"true\"");
if (nodep->isLatched()) puts(" latched=\"true\"");
if (nodep->isSigPublic()) puts(" public=\"true\"");

View File

@ -75,7 +75,7 @@ public:
CASEX, // Casex
CASTCONST, // Cast is constant
CDCRSTLOGIC, // Logic in async reset path
CLKDATA, // Clock used as data
CLKDATA, // Clock used as data. Historical, never issued.
CMPCONST, // Comparison is constant due to limited range
COLONPLUS, // :+ instead of +:
COMBDLY, // Combinatorial delayed assignment
@ -85,11 +85,11 @@ public:
DEPRECATED, // Feature will be deprecated
ENDLABEL, // End lable name mismatch
EOFNEWLINE, // End-of-file missing newline
GENCLK, // Generated Clock
GENCLK, // Generated Clock. Historical, never issued.
HIERBLOCK, // Ignored hierarchical block setting
IFDEPTH, // If statements too deep
IGNOREDRETURN, // Ignoring return value (function as task)
IMPERFECTSCH, // Imperfect schedule (disabled by default)
IMPERFECTSCH, // Imperfect schedule (disabled by default). Historical, never issued.
IMPLICIT, // Implicit wire
IMPORTSTAR, // Import::* in $unit
IMPURE, // Impure function not being inlined
@ -124,7 +124,7 @@ public:
TICKCOUNT, // Too large tick count
TIMESCALEMOD, // Need timescale for module
UNDRIVEN, // No drivers
UNOPT, // Unoptimizable block
UNOPT, // Unoptimizable block. Historical, never issued.
UNOPTFLAT, // Unoptimizable block after flattening
UNOPTTHREADS, // Thread partitioner unable to fill all requested threads
UNPACKED, // Unsupported unpacked

View File

@ -324,8 +324,6 @@ void FileLine::warnStyleOff(bool flag) {
bool FileLine::warnIsOff(V3ErrorCode code) const {
if (!m_warnOn.test(code)) return true;
if (!defaultFileLine().m_warnOn.test(code)) return true; // Global overrides local
// UNOPTFLAT implies UNOPT
if (code == V3ErrorCode::UNOPT && !m_warnOn.test(V3ErrorCode::UNOPTFLAT)) return true;
if ((code.lintError() || code.styleError()) && !m_warnOn.test(V3ErrorCode::I_LINT)) {
return true;
}

View File

@ -1,224 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Generated Clock repairs
//
// 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
//
//*************************************************************************
// GENCLK TRANSFORMATIONS:
// Follow control-flow graph with assignments and var usages
// ASSIGNDLY to variable later used as clock requires change detect
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Global.h"
#include "V3GenClk.h"
#include "V3Ast.h"
//######################################################################
// GenClk state, as a visitor of each AstNode
class GenClkBaseVisitor VL_NOT_FINAL : public VNVisitor {
protected:
VL_DEBUG_FUNC; // Declare debug()
};
//######################################################################
// GenClk Read
class GenClkRenameVisitor final : public GenClkBaseVisitor {
private:
// NODE STATE
// Cleared on top scope
// AstVarScope::user2() -> AstVarScope*. Signal replacing activation with
// AstVarRef::user3() -> bool. Signal is replaced activation (already done)
const VNUser2InUse m_inuser2;
const VNUser3InUse m_inuser3;
// STATE
const AstActive* m_activep = nullptr; // Inside activate statement
AstNodeModule* const m_topModp; // Top module
AstScope* const m_scopetopp = v3Global.rootp()->topScopep()->scopep(); // The top AstScope
// METHODS
AstVarScope* genInpClk(AstVarScope* vscp) {
if (!vscp->user2p()) {
// In order to create a __VinpClk* for a signal, it needs to be marked circular.
// The DPI export trigger is never marked circular by V3Order (see comments in
// OrderVisitor::nodeMarkCircular). The only other place where one might mark
// a node circular is in this pass (V3GenClk), if the signal is assigned but was
// previously used as a clock. The DPI export trigger is only ever assigned in
// a DPI export called from outside eval, or from a DPI import, which are not
// discovered by GenClkReadVisitor (note that impure tasks - i.e.: those setting
// non-local variables - cannot be no-inline, see V3Task), hence the DPI export
// trigger should never be marked circular. Note that ordering should still be
// correct as there will be a change detect on any signals set from a DPI export
// that might have dependents scheduled earlier.
UASSERT_OBJ(vscp != v3Global.rootp()->dpiExportTriggerp(), vscp,
"DPI export trigger should not need __VinpClk");
AstVar* const varp = vscp->varp();
const string newvarname
= "__VinpClk__" + vscp->scopep()->nameDotless() + "__" + varp->name();
// Create: VARREF(inpclk)
// ...
// ASSIGN(VARREF(inpclk), VARREF(var))
AstVar* const newvarp
= new AstVar(varp->fileline(), VVarType::MODULETEMP, newvarname, varp);
m_topModp->addStmtp(newvarp);
AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopetopp, newvarp);
m_scopetopp->addVarp(newvscp);
AstAssign* const asninitp = new AstAssign(
vscp->fileline(), new AstVarRef(vscp->fileline(), newvscp, VAccess::WRITE),
new AstVarRef(vscp->fileline(), vscp, VAccess::READ));
m_scopetopp->addFinalClkp(asninitp);
//
vscp->user2p(newvscp);
}
return VN_AS(vscp->user2p(), VarScope);
}
// VISITORS
virtual void visit(AstVarRef* nodep) override {
// Consumption/generation of a variable,
if (m_activep && !nodep->user3SetOnce()) {
AstVarScope* const vscp = nodep->varScopep();
if (vscp->isCircular()) {
UINFO(8, " VarActReplace " << nodep << endl);
// Replace with the new variable
AstVarScope* const newvscp = genInpClk(vscp);
AstVarRef* const newrefp
= new AstVarRef(nodep->fileline(), newvscp, nodep->access());
nodep->replaceWith(newrefp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
}
virtual void visit(AstActive* nodep) override {
m_activep = nodep;
iterate(nodep->sensesp());
m_activep = nullptr;
}
//-----
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
GenClkRenameVisitor(AstTopScope* nodep, AstNodeModule* topModp)
: m_topModp{topModp} {
iterate(nodep);
}
virtual ~GenClkRenameVisitor() override = default;
};
//######################################################################
// GenClk Read
class GenClkReadVisitor final : public GenClkBaseVisitor {
private:
// NODE STATE
// Cleared on top scope
// AstVarScope::user() -> bool. Set when the var has been used as clock
// STATE
bool m_tracingCall = false; // Iterating into a call to a cfunc
const AstActive* m_activep = nullptr; // Inside activate statement
const AstNodeAssign* m_assignp = nullptr; // Inside assigndly statement
AstNodeModule* m_topModp = nullptr; // Top module
// VISITORS
virtual void visit(AstTopScope* nodep) override {
{
const VNUser1InUse user1InUse;
iterateChildren(nodep);
}
// Make the new clock signals and replace any activate references
// See rename, it does some AstNode::userClearTree()'s
GenClkRenameVisitor{nodep, m_topModp};
}
virtual void visit(AstNodeModule* nodep) override {
// Only track the top scopes, not lower level functions
if (nodep->isTop()) {
m_topModp = nodep;
iterateChildren(nodep);
}
}
virtual void visit(AstNodeCCall* nodep) override {
iterateChildren(nodep);
if (!nodep->funcp()->entryPoint()) {
// Enter the function and trace it
m_tracingCall = true;
iterate(nodep->funcp());
}
}
virtual void visit(AstCFunc* nodep) override {
if (!m_tracingCall && !nodep->entryPoint()) {
// Only consider logic within a CFunc when looking
// at the call to it, and not when scanning whatever
// scope it happens to live beneath.
return;
}
m_tracingCall = false;
iterateChildren(nodep);
}
//----
virtual void visit(AstVarRef* nodep) override {
// Consumption/generation of a variable,
AstVarScope* const vscp = nodep->varScopep();
UASSERT_OBJ(vscp, nodep, "Scope not assigned");
if (m_activep) {
UINFO(8, " VarAct " << nodep << endl);
vscp->user1(true);
}
if (m_assignp && nodep->access().isWriteOrRW() && vscp->user1()) {
// Variable was previously used as a clock, and is now being set
// Thus a unordered generated clock...
UINFO(8, " VarSetAct " << nodep << endl);
vscp->circular(true);
}
}
virtual void visit(AstNodeAssign* nodep) override {
// UINFO(8, "ASS " << nodep << endl);
m_assignp = nodep;
iterateChildren(nodep);
m_assignp = nullptr;
}
virtual void visit(AstActive* nodep) override {
UINFO(8, "ACTIVE " << nodep << endl);
m_activep = nodep;
UASSERT_OBJ(nodep->sensesp(), nodep, "Unlinked");
iterate(nodep->sensesp());
m_activep = nullptr;
iterateChildren(nodep);
}
//-----
virtual void visit(AstVar*) override {} // Don't want varrefs under it
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit GenClkReadVisitor(AstNetlist* nodep) { iterate(nodep); }
virtual ~GenClkReadVisitor() override = default;
};
//######################################################################
// GenClk class functions
void V3GenClk::genClkAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
{ GenClkReadVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("genclk", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}

View File

@ -1,32 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Generated Clock Repairs
//
// 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 VERILATOR_V3GENCLK_H_
#define VERILATOR_V3GENCLK_H_
#include "config_build.h"
#include "verilatedos.h"
class AstNetlist;
//============================================================================
class V3GenClk final {
public:
static void genClkAll(AstNetlist* nodep);
};
#endif // Guard

View File

@ -104,6 +104,7 @@ class V3Global final {
// Experimenting with always requiring heavy, see (#2701)
bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols
bool m_dpi = false; // Need __Dpi include files
bool m_hasEvents = false; // Design uses SystemVerilog named events
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 needTraceDumper(bool flag) { m_needTraceDumper = flag; }
bool dpi() const { return m_dpi; }
void dpi(bool flag) { m_dpi = flag; }
bool hasEvents() const { return m_hasEvents; }
void setHasEvents() { m_hasEvents = true; }
bool hasForceableSignals() const { return m_hasForceableSignals; }
void setHasForceableSignals() { m_hasForceableSignals = true; }
bool hasSCTextSections() const { return m_hasSCTextSections; }

View File

@ -131,8 +131,8 @@ struct LifePostLocation {
class LifePostDlyVisitor final : public VNVisitor {
private:
// NODE STATE
// Cleared on entire tree
// AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var
// AstVarScope::user1() -> bool: referenced outside _eval__nba
// AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var
const VNUser4InUse m_inuser4;
// STATE
@ -155,6 +155,9 @@ private:
const V3Graph* m_mtasksGraphp = nullptr; // Mtask tracking graph
std::unique_ptr<GraphPathChecker> m_checker;
const AstCFunc* const m_evalNbap; // The _eval__nba function
bool m_inEvalNba = false; // Traversing under _eval__nba
// METHODS
VL_DEBUG_FUNC; // Declare debug()
@ -185,8 +188,11 @@ private:
return true;
}
void squashAssignposts() {
for (auto& itr : m_assignposts) {
const LifePostLocation* const app = &itr.second;
for (auto& pair : m_assignposts) {
// If referenced external to _eval__nba, don't optimize
if (pair.first->user1()) continue;
const LifePostLocation* const app = &pair.second;
const AstVarRef* const lhsp = VN_AS(app->nodep->lhsp(), VarRef); // original var
const AstVarRef* const rhsp = VN_AS(app->nodep->rhsp(), VarRef); // dly var
AstVarScope* const dlyVarp = rhsp->varScopep();
@ -274,6 +280,12 @@ private:
LifePostElimVisitor{nodep};
}
virtual void visit(AstVarRef* nodep) override {
// Mark variables referenced outside _eval__nba
if (!m_inEvalNba) {
nodep->varScopep()->user1(true);
return;
}
// Consumption/generation of a variable,
const AstVarScope* const vscp = nodep->varScopep();
UASSERT_OBJ(vscp, nodep, "Scope not assigned");
@ -288,6 +300,7 @@ private:
// The pre-assignment into the dly var should not count as its
// first write; we only want to consider reads and writes that
// would still happen if the dly var were eliminated.
if (!m_inEvalNba) iterateChildren(nodep);
}
virtual void visit(AstAssignPost* nodep) override {
// Don't record ASSIGNPOST in the read/write maps, record them in a
@ -315,9 +328,11 @@ private:
}
virtual void visit(AstExecGraph* nodep) override {
// Treat the ExecGraph like a call to each mtask body
UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
m_mtasksGraphp = nodep->depGraphp();
for (V3GraphVertex* mtaskVxp = m_mtasksGraphp->verticesBeginp(); mtaskVxp;
if (m_inEvalNba) {
UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
m_mtasksGraphp = nodep->depGraphp();
}
for (V3GraphVertex* mtaskVxp = nodep->depGraphp()->verticesBeginp(); mtaskVxp;
mtaskVxp = mtaskVxp->verticesNextp()) {
const ExecMTask* const mtaskp = dynamic_cast<ExecMTask*>(mtaskVxp);
m_execMTaskp = mtaskp;
@ -328,6 +343,8 @@ private:
}
virtual void visit(AstCFunc* nodep) override {
if (!m_tracingCall && !nodep->entryPoint()) return;
VL_RESTORER(m_inEvalNba);
if (nodep == m_evalNbap) m_inEvalNba = true;
m_tracingCall = false;
iterateChildren(nodep);
}
@ -337,7 +354,10 @@ private:
public:
// CONSTRUCTORS
explicit LifePostDlyVisitor(AstNetlist* nodep) { iterate(nodep); }
explicit LifePostDlyVisitor(AstNetlist* netlistp)
: m_evalNbap{netlistp->evalNbap()} {
iterate(netlistp);
}
virtual ~LifePostDlyVisitor() override {
V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel);
}

View File

@ -92,6 +92,13 @@ private:
iterateAndNextNull(nodep->lhsp());
}
}
virtual void visit(AstFireEvent* nodep) override {
VL_RESTORER(m_setRefLvalue);
{
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->operandp());
}
}
virtual void visit(AstCastDynamic* nodep) override {
VL_RESTORER(m_setRefLvalue);
{

View File

@ -305,7 +305,7 @@ private:
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_CLOCK_ENABLE) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrClockEn(true);
nodep->v3warn(DEPRECATED, "'clock_enable' attribute is deprecated and has no effect");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FORCEABLE) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");

View File

@ -148,39 +148,8 @@ private:
virtual void visit(AstSenItem* nodep) override {
// Remove bit selects, and bark if it's not a simple variable
iterateChildren(nodep);
if (nodep->isClocked()) {
// If it's not a simple variable wrap in a temporary
// This is a bit unfortunate as we haven't done width resolution
// and any width errors will look a bit odd, but it works.
AstNode* const sensp = nodep->sensp();
if (sensp && !VN_IS(sensp, NodeVarRef) && !VN_IS(sensp, Const)) {
// Make a new temp wire
const string newvarname = "__Vsenitemexpr" + cvtToStr(++m_senitemCvtNum);
AstVar* const newvarp = new AstVar(sensp->fileline(), VVarType::MODULETEMP,
newvarname, VFlagLogicPacked(), 1);
// We can't just add under the module, because we may be
// inside a generate, begin, etc.
// We know a SenItem should be under a SenTree/Always etc,
// we we'll just hunt upwards
AstNode* addwherep = nodep; // Add to this element's next
while (VN_IS(addwherep, SenItem) || VN_IS(addwherep, SenTree)) {
addwherep = addwherep->backp();
}
if (!VN_IS(addwherep, Always)) { // Assertion perhaps?
sensp->v3warn(E_UNSUPPORTED,
"Unsupported: Non-single-bit pos/negedge clock statement under "
"some complicated block");
addwherep = m_modp;
}
addwherep->addNext(newvarp);
sensp->replaceWith(new AstVarRef(sensp->fileline(), newvarp, VAccess::READ));
AstAssignW* const assignp = new AstAssignW(
sensp->fileline(), new AstVarRef(sensp->fileline(), newvarp, VAccess::WRITE),
sensp);
addwherep->addNext(assignp);
}
} else { // Old V1995 sensitivity list; we'll probably mostly ignore
if (!nodep->isClocked()) {
// Old V1995 sensitivity list; we'll probably mostly ignore
bool did = true;
while (did) {
did = false;
@ -205,9 +174,7 @@ private:
}
}
}
if (!VN_IS(nodep->sensp(), NodeVarRef)
&& !VN_IS(nodep->sensp(), EnumItemRef) // V3Const will cleanup
&& !nodep->isIllegal()) {
if (nodep->isIllegal()) {
if (debug()) nodep->dumpTree(cout, "-tree: ");
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Complex statement in sensitivity list");
}

View File

@ -1192,7 +1192,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char
}
});
DECL_OPTION("-o", Set, &m_exeName);
DECL_OPTION("-order-clock-delay", OnOff, &m_orderClockDly);
DECL_OPTION("-order-clock-delay", CbOnOff, [fl](bool flag) {
fl->v3warn(DEPRECATED, "Option order-clock-delay is deprecated and has no effect.");
});
DECL_OPTION("-output-split", Set, &m_outputSplit);
DECL_OPTION("-output-split-cfuncs", CbVal, [this, fl](const char* valp) {
m_outputSplitCFuncs = std::atoi(valp);

View File

@ -246,7 +246,6 @@ private:
bool m_gmake = false; // main switch: --make gmake
bool m_main = false; // main swithc: --main
bool m_mergeConstPool = true; // main switch: --merge-const-pool
bool m_orderClockDly = true; // main switch: --order-clock-delay
bool m_outFormatOk = false; // main switch: --cc, --sc or --sp was specified
bool m_pedantic = false; // main switch: --Wpedantic
bool m_pinsScUint = false; // main switch: --pins-sc-uint
@ -459,7 +458,6 @@ public:
bool traceUnderscore() const { return m_traceUnderscore; }
bool main() const { return m_main; }
bool mergeConstPool() const { return m_mergeConstPool; }
bool orderClockDly() const { return m_orderClockDly; }
bool outFormatOk() const { return m_outFormatOk; }
bool keepTempFiles() const { return (V3Error::debugDefault() != 0); }
bool pedantic() const { return m_pedantic; }

File diff suppressed because it is too large Load Diff

View File

@ -20,13 +20,32 @@
#include "config_build.h"
#include "verilatedos.h"
#include <functional>
#include <unordered_map>
#include <vector>
class AstCFunc;
class AstNetlist;
class AstSenItem;
class AstSenTree;
class AstVarScope;
namespace V3Sched {
struct LogicByScope;
};
//============================================================================
class V3Order final {
public:
static void orderAll(AstNetlist* nodep);
};
namespace V3Order {
AstCFunc* order(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& logic, //
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen,
const string& tag, //
bool parallel, //
bool slow, //
std::function<AstSenTree*(const AstVarScope*)> externalDomain);
}; // namespace V3Order
#endif // Guard

View File

@ -19,7 +19,6 @@
// OrderMoveVertex
// MTaskMoveVertex
// OrderEitherVertex
// OrderInputsVertex
// OrderLogicVertex
// OrderVarVertex
// OrderVarStdVertex
@ -29,7 +28,6 @@
//
// V3GraphEdge
// OrderEdge
// OrderComboCutEdge
// OrderPostCutEdge
// OrderPreCutEdge
//*************************************************************************
@ -53,7 +51,6 @@ class OrderMoveDomScope;
//######################################################################
enum OrderWeights : uint8_t {
WEIGHT_INPUT = 1, // Low weight just so dot graph looks nice
WEIGHT_COMBO = 1, // Breakable combo logic
WEIGHT_POST = 2, // Post-delayed used var
WEIGHT_PRE = 3, // Breakable pre-delayed used var
@ -64,13 +61,11 @@ enum OrderWeights : uint8_t {
struct OrderVEdgeType {
enum en : uint8_t {
VERTEX_UNKNOWN = 0,
VERTEX_INPUTS,
VERTEX_LOGIC,
VERTEX_VARSTD,
VERTEX_VARPRE,
VERTEX_VARPOST,
VERTEX_VARPORD,
VERTEX_VARSETTLE,
VERTEX_MOVE,
EDGE_STD,
EDGE_COMBOCUT,
@ -80,10 +75,9 @@ struct OrderVEdgeType {
};
const char* ascii() const {
static const char* const names[]
= {"%E-vedge", "VERTEX_INPUTS", "VERTEX_LOGIC", "VERTEX_VARSTD",
"VERTEX_VARPRE", "VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_VARSETTLE",
"VERTEX_MOVE", "EDGE_STD", "EDGE_COMBOCUT", "EDGE_PRECUT",
"EDGE_POSTCUT", "_ENUM_END"};
= {"%E-vedge", "VERTEX_LOGIC", "VERTEX_VARSTD", "VERTEX_VARPRE",
"VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_MOVE", "EDGE_STD",
"EDGE_COMBOCUT", "EDGE_PRECUT", "EDGE_POSTCUT", "_ENUM_END"};
return names[m_e];
}
enum en m_e;
@ -123,13 +117,11 @@ public:
class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex {
AstScope* const m_scopep; // Scope the vertex is in
AstSenTree* m_domainp; // Clock domain (nullptr = to be computed as we iterate)
bool m_isFromInput = false; // From input, or derived therefrom (conservatively false)
protected:
OrderEitherVertex(V3Graph* graphp, const OrderEitherVertex& old)
: V3GraphVertex{graphp, old}
, m_scopep{old.m_scopep}
, m_domainp{old.m_domainp}
, m_isFromInput{old.m_isFromInput} {}
, m_domainp{old.m_domainp} {}
public:
OrderEitherVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp)
@ -146,43 +138,26 @@ public:
void domainp(AstSenTree* domainp) { m_domainp = domainp; }
AstScope* scopep() const { return m_scopep; }
AstSenTree* domainp() const { return m_domainp; }
void isFromInput(bool flag) { m_isFromInput = flag; }
bool isFromInput() const { return m_isFromInput; }
};
class OrderInputsVertex final : public OrderEitherVertex {
OrderInputsVertex(V3Graph* graphp, const OrderInputsVertex& old)
: OrderEitherVertex{graphp, old} {}
public:
OrderInputsVertex(V3Graph* graphp, AstSenTree* domainp)
: OrderEitherVertex{graphp, nullptr, domainp} {
isFromInput(true); // By definition
}
virtual ~OrderInputsVertex() override = default;
virtual OrderInputsVertex* clone(V3Graph* graphp) const override {
return new OrderInputsVertex(graphp, *this);
}
virtual OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_INPUTS; }
virtual string name() const override { return "*INPUTS*"; }
virtual string dotColor() const override { return "green"; }
virtual string dotName() const override { return ""; }
virtual string dotShape() const override { return "invhouse"; }
virtual bool domainMatters() override { return false; }
};
class OrderLogicVertex final : public OrderEitherVertex {
AstNode* const m_nodep;
AstSenTree* const m_hybridp;
protected:
OrderLogicVertex(V3Graph* graphp, const OrderLogicVertex& old)
: OrderEitherVertex{graphp, old}
, m_nodep{old.m_nodep} {}
, m_nodep{old.m_nodep}
, m_hybridp{old.m_hybridp} {}
public:
OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstNode* nodep)
OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstSenTree* hybridp,
AstNode* nodep)
: OrderEitherVertex{graphp, scopep, domainp}
, m_nodep{nodep} {}
, m_nodep{nodep}
, m_hybridp{hybridp} {
UASSERT_OBJ(!(domainp && hybridp), nodep, "Can't have bot domainp and hybridp set");
}
virtual ~OrderLogicVertex() override = default;
virtual OrderLogicVertex* clone(V3Graph* graphp) const override {
return new OrderLogicVertex(graphp, *this);
@ -194,6 +169,7 @@ public:
return (cvtToHex(m_nodep) + "\\n " + cvtToStr(nodep()->typeName()));
}
AstNode* nodep() const { return m_nodep; }
AstSenTree* hybridp() const { return m_hybridp; }
virtual string dotShape() const override {
return VN_IS(m_nodep, Active) ? "doubleoctagon" : "rect";
}
@ -201,14 +177,11 @@ public:
class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex {
AstVarScope* const m_varScp;
bool m_isClock = false; // Used as clock
bool m_isDelayed = false; // Set in a delayed assignment
protected:
OrderVarVertex(V3Graph* graphp, const OrderVarVertex& old)
: OrderEitherVertex{graphp, old}
, m_varScp{old.m_varScp}
, m_isClock{old.m_isClock}
, m_isDelayed{old.m_isDelayed} {}
, m_varScp{old.m_varScp} {}
public:
OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
@ -220,10 +193,6 @@ public:
virtual FileLine* fileline() const override { return varScp()->fileline(); }
// ACCESSORS
AstVarScope* varScp() const { return m_varScp; }
void isClock(bool flag) { m_isClock = flag; }
bool isClock() const { return m_isClock; }
void isDelayed(bool flag) { m_isDelayed = flag; }
bool isDelayed() const { return m_isDelayed; }
virtual string dotShape() const override { return "ellipse"; }
};
@ -445,35 +414,6 @@ public:
V3GraphVertex* top) const override {
return new OrderEdge(graphp, fromp, top, *this);
}
// When ordering combo blocks with stronglyConnected, follow edges not
// involving pre/pos variables
virtual bool followComboConnected() const { return true; }
static bool followComboConnected(const V3GraphEdge* edgep) {
const OrderEdge* const oedgep = dynamic_cast<const OrderEdge*>(edgep);
if (!oedgep) v3fatalSrc("Following edge of non-OrderEdge type");
return (oedgep->followComboConnected());
}
};
class OrderComboCutEdge final : public OrderEdge {
// Edge created from output of combo logic
// Breakable if the output var is also a input,
// in which case we'll need a change detect loop around this var.
OrderComboCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top,
const OrderComboCutEdge& old)
: OrderEdge{graphp, fromp, top, old} {}
public:
OrderComboCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: OrderEdge{graphp, fromp, top, WEIGHT_COMBO, CUTABLE} {}
virtual OrderVEdgeType type() const override { return OrderVEdgeType::EDGE_COMBOCUT; }
virtual ~OrderComboCutEdge() override = default;
virtual OrderComboCutEdge* clone(V3Graph* graphp, V3GraphVertex* fromp,
V3GraphVertex* top) const override {
return new OrderComboCutEdge(graphp, fromp, top, *this);
}
virtual string dotColor() const override { return "yellowGreen"; }
virtual bool followComboConnected() const override { return true; }
};
class OrderPostCutEdge final : public OrderEdge {
@ -494,7 +434,6 @@ public:
return new OrderPostCutEdge(graphp, fromp, top, *this);
}
virtual string dotColor() const override { return "PaleGreen"; }
virtual bool followComboConnected() const override { return false; }
};
class OrderPreCutEdge final : public OrderEdge {
@ -515,7 +454,6 @@ public:
}
virtual ~OrderPreCutEdge() override = default;
virtual string dotColor() const override { return "khaki"; }
virtual bool followComboConnected() const override { return false; }
};
#endif // Guard

View File

@ -1841,7 +1841,6 @@ private:
for (V3GraphEdge* edgep = (*ovvIt)->inBeginp(); edgep; edgep = edgep->inNextp()) {
const OrderLogicVertex* const logicp = dynamic_cast<OrderLogicVertex*>(edgep->fromp());
if (!logicp) continue;
if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue;
LogicMTask* const writerMtaskp = m_olv2mtask.at(logicp);
(*tasksByRankp)[writerMtaskp->rank()].insert(writerMtaskp);
}
@ -1849,7 +1848,6 @@ private:
for (V3GraphEdge* edgep = (*ovvIt)->outBeginp(); edgep; edgep = edgep->outNextp()) {
const OrderLogicVertex* const logicp = dynamic_cast<OrderLogicVertex*>(edgep->fromp());
if (!logicp) continue;
if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue;
LogicMTask* const readerMtaskp = m_olv2mtask.at(logicp);
(*tasksByRankp)[readerMtaskp->rank()].insert(readerMtaskp);
}
@ -2563,6 +2561,11 @@ void V3Partition::go(V3Graph* mtasksp) {
// (and the logic nodes therein.)
uint32_t totalGraphCost = 0;
{
// Artificial single entry point vertex in the MTask graph to allow sibling merges.
// This is required as otherwise disjoint sub-graphs could not be merged, but the
// coarsening algorithm assumes that the graph is connected.
LogicMTask* const entryMTask = new LogicMTask{mtasksp, nullptr};
// The V3InstrCount within LogicMTask will set user5 on each AST
// node, to assert that we never count any node twice.
const VNUser5InUse inUser5;
@ -2578,8 +2581,21 @@ void V3Partition::go(V3Graph* mtasksp) {
totalGraphCost += mtaskp->cost();
}
// Artificial single exit point vertex in the MTask graph to allow sibling merges.
// this enables merging MTasks with no downstream dependents if that is the ideal merge.
LogicMTask* const exitMTask = new LogicMTask{mtasksp, nullptr};
// Create the mtask->mtask dep edges based on vertex deps
setupMTaskDeps(mtasksp, &vx2mtask);
// Add the entry/exit edges
for (V3GraphVertex* vtxp = mtasksp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
if (vtxp == entryMTask) continue;
if (vtxp == exitMTask) continue;
LogicMTask* const lmtp = static_cast<LogicMTask*>(vtxp);
if (vtxp->inEmpty()) new MTaskEdge{mtasksp, entryMTask, lmtp, 1};
if (vtxp->outEmpty()) new MTaskEdge{mtasksp, lmtp, exitMTask, 1};
}
}
V3Partition::debugMTaskGraphStats(mtasksp, "initial");

1047
src/V3Sched.cpp Normal file

File diff suppressed because it is too large Load Diff

126
src/V3Sched.h Normal file
View File

@ -0,0 +1,126 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: 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
//
//*************************************************************************
#ifndef VERILATOR_V3SCHED_H_
#define VERILATOR_V3SCHED_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include <functional>
#include <utility>
#include <vector>
//============================================================================
namespace V3Sched {
//============================================================================
// Throughout scheduling, we need to keep hold of AstActive nodes, together with the AstScope that
// they are under. LogicByScope is simply a vector of such pairs, with some additional convenience
// methods.
struct LogicByScope final : public std::vector<std::pair<AstScope*, AstActive*>> {
// Add logic
void add(AstScope* scopep, AstSenTree* senTreep, AstNode* logicp) {
UASSERT_OBJ(!logicp->backp(), logicp, "Already linked");
if (empty() || back().first != scopep || back().second->sensesp() != senTreep) {
emplace_back(scopep, new AstActive{logicp->fileline(), "", senTreep});
}
back().second->addStmtsp(logicp);
};
// Create copy, with the AstActives cloned
LogicByScope clone() const {
LogicByScope result;
for (const auto& pair : *this) {
result.emplace_back(pair.first, pair.second->cloneTree(false));
}
return result;
}
// Delete actives (they should all be empty)
void deleteActives() {
for (const auto& pair : *this) {
AstActive* const activep = pair.second;
UASSERT_OBJ(!activep->stmtsp(), activep, "Leftover logic");
if (activep->backp()) activep->unlinkFrBack();
activep->deleteTree();
}
clear();
};
void foreachLogic(std::function<void(AstNode*)> f) const {
for (const auto& pair : *this) {
for (AstNode* nodep = pair.second->stmtsp(); nodep; nodep = nodep->nextp()) f(nodep);
}
}
};
// Logic in the design is classified based on what can trigger its execution.
// For details see the internals documentation.
struct LogicClasses final {
LogicByScope m_static; // Static variable initializers
LogicByScope m_initial; // initial blocks
LogicByScope m_final; // final blocks
LogicByScope m_comb; // Combinational logic (logic with implicit sensitivities)
LogicByScope m_clocked; // Clocked (or sequential) logic (logic with explictit sensitivities)
LogicByScope m_hybrid; // Hybrid logic (combinational logic with some explicit sensitivities)
LogicClasses() = default;
VL_UNCOPYABLE(LogicClasses);
LogicClasses(LogicClasses&&) = default;
LogicClasses& operator=(LogicClasses&&) = default;
};
// Combinational (including hybrid) logic, and clocked logic in partitioned to compute all clock
// signals in the 'act' region. For details see the internals documentation.
struct LogicRegions final {
LogicByScope m_pre; // AstAssignPre logic in 'act' region
LogicByScope m_act; // 'act' region logic
LogicByScope m_nba; // 'nba' region logic
LogicRegions() = default;
VL_UNCOPYABLE(LogicRegions);
LogicRegions(LogicRegions&&) = default;
LogicRegions& operator=(LogicRegions&&) = default;
};
// Combinational (including hybrid) logic is replicated into the various scheduling regions.
// For details see the internals documentation.
struct LogicReplicas final {
LogicByScope m_ico; // Logic replicated into the 'ico' (Input Combinational) region
LogicByScope m_act; // Logic replicated into the 'act' region
LogicByScope m_nba; // Logic replicated into the 'nba' region
LogicReplicas() = default;
VL_UNCOPYABLE(LogicReplicas);
LogicReplicas(LogicReplicas&&) = default;
LogicReplicas& operator=(LogicReplicas&&) = default;
};
// Top level entry point to scheduling
void schedule(AstNetlist*);
// Sub-steps
LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic);
LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
LogicByScope& hybridLogic);
LogicReplicas replicateLogic(LogicRegions&);
} // namespace V3Sched
#endif // Guard

421
src/V3SchedAcyclic.cpp Normal file
View File

@ -0,0 +1,421 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Scheduling - break combinational cycles
//
// 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
//
//*************************************************************************
//
// Combinational loops are broken by the introduction of instances of the
// 'hybrid' logic. Hybrid logic is like combinational logic, but also has
// explicit sensitivities. Any explicit sensitivity of hybrid logic suppresses
// the implicit sensitivity of the logic on the same variable. This enables us
// to cut combinational logic loops and perform ordering as if the logic is
// acyclic. See the internals documentation for more details.
//
// To achieve this we build a dependency graph of all combinational logic in
// the design, and then breaks all combinational cycles by converting all
// combinational logic that consumes a variable driven via a 'back-edge' into
// hybrid logic. Here back-edge' just means a graph edge that points from a
// higher rank vertex to a lower rank vertex in some consistent ranking of
// the directed graph. Variables driven via a back-edge in the dependency
// graph are marked, and all combinational logic that depends on such
// variables is converted into hybrid logic, with the back-edge driven
// variables listed as explicit 'changed' sensitivities.
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Error.h"
#include "V3Global.h"
#include "V3SenTree.h"
#include "V3Sched.h"
#include "V3SplitVar.h"
#include "V3Stats.h"
#include "V3Graph.h"
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
namespace V3Sched {
namespace {
//##############################################################################
// Data structures (graph types)
class LogicVertex final : public V3GraphVertex {
AstNode* const m_logicp; // The logic node this vertex represents
AstScope* const m_scopep; // The enclosing AstScope of the logic node
public:
LogicVertex(V3Graph* graphp, AstNode* logicp, AstScope* scopep)
: V3GraphVertex{graphp}
, m_logicp{logicp}
, m_scopep{scopep} {}
AstNode* logicp() const { return m_logicp; }
AstScope* scopep() const { return m_scopep; }
// For graph dumping
string name() const override { return m_logicp->fileline()->ascii(); };
string dotShape() const override { return "rectangle"; }
};
class VarVertex final : public V3GraphVertex {
AstVarScope* const m_vscp; // The AstVarScope this vertex represents
public:
VarVertex(V3Graph* graphp, AstVarScope* vscp)
: V3GraphVertex{graphp}
, m_vscp{vscp} {}
AstVarScope* vscp() const { return m_vscp; }
AstVar* varp() const { return m_vscp->varp(); }
// For graph dumping
string name() const override { return m_vscp->name(); }
string dotShape() const override { return "ellipse"; }
string dotColor() const override { return "blue"; }
};
class Graph final : public V3Graph {
void loopsVertexCb(V3GraphVertex* vtxp) {
// TODO: 'typeName' is an internal thing. This should be more human readable.
if (LogicVertex* const lvtxp = dynamic_cast<LogicVertex*>(vtxp)) {
AstNode* const logicp = lvtxp->logicp();
std::cerr << logicp->fileline()->warnOther()
<< " Example path: " << logicp->typeName() << endl;
}
if (VarVertex* const vvtxp = dynamic_cast<VarVertex*>(vtxp)) {
AstVarScope* const vscp = vvtxp->vscp();
std::cerr << vscp->fileline()->warnOther()
<< " Example path: " << vscp->prettyName() << endl;
}
}
};
//##############################################################################
// Algorithm implementation
std::unique_ptr<Graph> buildGraph(const LogicByScope& lbs) {
std::unique_ptr<Graph> graphp{new Graph};
// AstVarScope::user1() -> VarVertx
const VNUser1InUse user1InUse;
const auto getVarVertex = [&](AstVarScope* vscp) {
if (!vscp->user1p()) vscp->user1p(new VarVertex{graphp.get(), vscp});
return vscp->user1u().to<VarVertex*>();
};
const auto addEdge = [&](V3GraphVertex* fromp, V3GraphVertex* top, int weight, bool cuttable) {
new V3GraphEdge{graphp.get(), fromp, top, weight, cuttable};
};
for (const auto& pair : lbs) {
AstScope* const scopep = pair.first;
AstActive* const activep = pair.second;
UASSERT_OBJ(activep->hasCombo(), activep, "Not combinational logic");
for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
// Can safely ignore Postponed as we generate them all
if (VN_IS(nodep, AlwaysPostponed)) continue;
LogicVertex* const lvtxp = new LogicVertex{graphp.get(), nodep, scopep};
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
nodep->foreach<AstVarRef>([&](AstVarRef* refp) {
AstVarScope* const vscp = refp->varScopep();
VarVertex* const vvtxp = getVarVertex(vscp);
// We want to cut the narrowest signals
const int weight = vscp->width() / 8 + 1;
// If written, add logic -> var edge
if (refp->access().isWriteOrRW() && !vscp->user2SetOnce())
addEdge(lvtxp, vvtxp, weight, true);
// If read, add var -> logic edge
// Note: Use same heuristic as ordering does to ignore written variables
// TODO: Use live variable analysis.
if (refp->access().isReadOrRW() && !vscp->user3SetOnce() && !vscp->user2())
addEdge(vvtxp, lvtxp, weight, true);
});
}
}
return graphp;
}
void removeNonCyclic(Graph* graphp) {
// Work queue
std::vector<V3GraphVertex*> queue;
const auto enqueue = [&](V3GraphVertex* vtxp) {
if (vtxp->user()) return; // Already in queue
vtxp->user(1);
queue.push_back(vtxp);
};
// Start with vertices with no inputs or outputs
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
if (vtxp->inEmpty() || vtxp->outEmpty()) enqueue(vtxp);
}
// Iterate while we still have candidates
while (!queue.empty()) {
// Pop next candidate
V3GraphVertex* const vtxp = queue.back();
queue.pop_back();
vtxp->user(0); // No longer in queue
if (vtxp->inEmpty()) {
// Enqueue children for consideration, remove out edges, and delete this vertex
for (V3GraphEdge *edgep = vtxp->outBeginp(), *nextp; edgep; edgep = nextp) {
nextp = edgep->outNextp();
enqueue(edgep->top());
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
}
VL_DO_DANGLING(vtxp->unlinkDelete(graphp), vtxp);
} else if (vtxp->outEmpty()) {
// Enqueue parents for consideration, remove in edges, and delete this vertex
for (V3GraphEdge *edgep = vtxp->inBeginp(), *nextp; edgep; edgep = nextp) {
nextp = edgep->inNextp();
enqueue(edgep->fromp());
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
}
VL_DO_DANGLING(vtxp->unlinkDelete(graphp), vtxp);
}
}
}
// Has this VarVertex been cut? (any edges in or out has been cut)
bool isCut(const VarVertex* vtxp) {
for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
if (edgep->weight() == 0) return true;
}
for (V3GraphEdge* edgep = vtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
if (edgep->weight() == 0) return true;
}
return false;
}
std::vector<VarVertex*> findCutVertices(Graph* graphp) {
std::vector<VarVertex*> result;
const VNUser1InUse user1InUse; // bool: already added to result
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
if (VarVertex* const vvtxp = dynamic_cast<VarVertex*>(vtxp)) {
if (!vvtxp->vscp()->user1SetOnce() && isCut(vvtxp)) result.push_back(vvtxp);
}
}
return result;
}
void resetEdgeWeights(const std::vector<VarVertex*>& cutVertices) {
for (VarVertex* const vvtxp : cutVertices) {
for (V3GraphEdge* ep = vvtxp->inBeginp(); ep; ep = ep->inNextp()) ep->weight(1);
for (V3GraphEdge* ep = vvtxp->outBeginp(); ep; ep = ep->outNextp()) ep->weight(1);
}
}
// A VarVertex together with its fanout
using Candidate = std::pair<VarVertex*, unsigned>;
// Gather all splitting candidates that are in the same SCC as the given vertex
void gatherSCCCandidates(V3GraphVertex* vtxp, std::vector<Candidate>& candidates) {
if (vtxp->user()) return; // Already done
vtxp->user(true);
if (VarVertex* const vvtxp = dynamic_cast<VarVertex*>(vtxp)) {
AstVar* const varp = vvtxp->varp();
const string name = varp->prettyName();
if (!varp->user3SetOnce() // Only consider each AstVar once
&& varp->width() != 1 // Ignore 1-bit signals (they cannot be split further)
&& name.find("__Vdly") == string::npos // Ignore internal signals
&& name.find("__Vcell") == string::npos) {
// Also compute the fanout of this vertex
unsigned fanout = 0;
for (V3GraphEdge* ep = vtxp->outBeginp(); ep; ep = ep->outNextp()) ++fanout;
candidates.emplace_back(vvtxp, fanout);
}
}
// Iterate through all the vertices within the same strongly connected component (same color)
for (V3GraphEdge* edgep = vtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
V3GraphVertex* const top = edgep->top();
if (top->color() == vtxp->color()) gatherSCCCandidates(top, candidates);
}
for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
V3GraphVertex* const fromp = edgep->fromp();
if (fromp->color() == vtxp->color()) gatherSCCCandidates(fromp, candidates);
}
}
// Find all variables in a loop (SCC) that are candidates for splitting to break loops.
void reportLoopVars(Graph* graphp, VarVertex* vvtxp) {
// Vector of variables in UNOPTFLAT loop that are candidates for splitting.
std::vector<Candidate> candidates;
{
// AstNode::user3 is used to mark if we have done a particular variable.
// V3GraphVertex::user is used to mark if we have seen this vertex before.
const VNUser3InUse user3InUse;
graphp->userClearVertices();
gatherSCCCandidates(vvtxp, candidates);
graphp->userClearVertices();
}
// Possible we only have candidates the user cannot do anything about, so don't bother them.
if (candidates.empty()) return;
// There may be a very large number of candidates, so only report up to 10 of the "most
// important" signals.
unsigned splittable = 0;
const auto reportFirst10 = [&](std::function<bool(const Candidate&, const Candidate&)> less) {
std::stable_sort(candidates.begin(), candidates.end(), less);
for (size_t i = 0; i < 10; i++) {
if (i == candidates.size()) break;
const Candidate& candidate = candidates[i];
AstVar* const varp = candidate.first->varp();
std::cerr << V3Error::warnMore() << " " << varp->fileline() << " "
<< varp->prettyName() << ", width " << std::dec << varp->width()
<< ", circular fanout " << candidate.second;
if (V3SplitVar::canSplitVar(varp)) {
std::cerr << ", can split_var";
++splittable;
}
std::cerr << '\n';
}
};
// Widest variables
std::cerr << V3Error::warnMore() << "... Widest variables candidate to splitting:\n";
reportFirst10([](const Candidate& a, const Candidate& b) {
return a.first->varp()->width() > b.first->varp()->width();
});
// Highest fanout
std::cerr << V3Error::warnMore() << "... Candidates with the highest fanout:\n";
reportFirst10([](const Candidate& a, const Candidate& b) { //
return a.second > b.second;
});
if (splittable) {
std::cerr << V3Error::warnMore()
<< "... Suggest add /*verilator split_var*/ to appropriate variables above."
<< std::endl;
}
V3Stats::addStat("Scheduling, split_var, candidates", splittable);
}
void reportCycles(Graph* graphp, const std::vector<VarVertex*>& cutVertices) {
for (VarVertex* vvtxp : cutVertices) {
AstVarScope* const vscp = vvtxp->vscp();
FileLine* const flp = vscp->fileline();
// First v3warn not inside warnIsOff so we can see the suppressions with --debug
vscp->v3warn(UNOPTFLAT, "Signal unoptimizable: Circular combinational logic: "
<< vscp->prettyNameQ());
if (!flp->warnIsOff(V3ErrorCode::UNOPTFLAT) && !flp->lastWarnWaived()) {
// Complain just once
flp->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true);
// Calls Graph::loopsVertexCb
graphp->reportLoops(&V3GraphEdge::followAlwaysTrue, vvtxp);
if (v3Global.opt.reportUnoptflat()) {
// Report candidate variables for splitting
reportLoopVars(graphp, vvtxp);
// Create a subgraph for the UNOPTFLAT loop
V3Graph loopGraph;
graphp->subtreeLoops(&V3GraphEdge::followAlwaysTrue, vvtxp, &loopGraph);
loopGraph.dumpDotFilePrefixedAlways("unoptflat");
}
}
}
}
LogicByScope fixCuts(AstNetlist* netlistp, const std::vector<VarVertex*>& cutVertices) {
// For all logic that reads a cut vertex, build a map from logic -> list of cut AstVarScope
// they read. Also build a vector of the involved logic for deterministic results.
std::unordered_map<LogicVertex*, std::vector<AstVarScope*>> lvtx2Cuts;
std::vector<LogicVertex*> lvtxps;
{
const VNUser1InUse user1InUse; // bool: already added to 'lvtxps'
for (VarVertex* const vvtxp : cutVertices) {
for (V3GraphEdge* edgep = vvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
LogicVertex* const lvtxp = static_cast<LogicVertex*>(edgep->top());
if (!lvtxp->logicp()->user1SetOnce()) lvtxps.push_back(lvtxp);
lvtx2Cuts[lvtxp].push_back(vvtxp->vscp());
}
}
}
// Make the logic reading cut vertices use a hybrid sensitivity (combinational, but with some
// explicit additional triggers on the cut variables)
LogicByScope result;
SenTreeFinder finder{netlistp};
for (LogicVertex* const lvtxp : lvtxps) {
AstNode* const logicp = lvtxp->logicp();
logicp->unlinkFrBack();
FileLine* const flp = logicp->fileline();
// Build the hybrid sensitivity list
AstSenItem* senItemsp = nullptr;
for (AstVarScope* const vscp : lvtx2Cuts[lvtxp]) {
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ};
AstSenItem* const nextp = new AstSenItem{flp, VEdgeType::ET_HYBRID, refp};
if (!senItemsp) {
senItemsp = nextp;
} else {
senItemsp->addNext(nextp);
}
}
AstSenTree* const senTree = new AstSenTree{flp, senItemsp};
// Add logic to result with new sensitivity
result.add(lvtxp->scopep(), finder.getSenTree(senTree), logicp);
// SenTreeFinder::getSenTree clones, so clean up
VL_DO_DANGLING(senTree->deleteTree(), senTree);
}
return result;
}
} // namespace
LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic) {
// Build the dataflow (dependency) graph
const std::unique_ptr<Graph> graphp = buildGraph(combinationalLogic);
// Remove nodes that don't form part of a cycle
removeNonCyclic(graphp.get());
// Nothing to do if no cycles, yay!
if (graphp->empty()) return LogicByScope{};
// Dump for debug
if (debug() > 6) graphp->dumpDotFilePrefixed("sched-comb-cycles");
// Make graph acyclic by cutting some edges. Note: This also colors strongly connected
// components which reportCycles uses to print each SCCs separately.
// TODO: A more optimal algorithm that cuts by removing/marking VarVertex vertices is possible
// Search for "Feedback vertex set" (current algorithm is "Feedback arc set")
graphp->acyclic(&V3GraphEdge::followAlwaysTrue);
// Find all cut vertices
const std::vector<VarVertex*> cutVertices = findCutVertices(graphp.get());
// Reset edge weights for reporting
resetEdgeWeights(cutVertices);
// Report warnings/diagnostics
reportCycles(graphp.get(), cutVertices);
// Fix cuts by converting dependent logic to use hybrid sensitivities
return fixCuts(netlistp, cutVertices);
}
} // namespace V3Sched

400
src/V3SchedPartition.cpp Normal file
View File

@ -0,0 +1,400 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Scheduling - partitioning
//
// 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
//
//*************************************************************************
//
// V3SchedPartition (and in particular V3Sched::partition) partitions all
// logic into two regions, the 'act' region contains all logic that might
// compute a clock via an update even that falls into the SystemVerilog Active
// scheduling region (that is: blocking and continuous assignments in
// particular). All other logic is assigned to the 'nba' region.
//
// To achieve this, we build a dependency graph of all logic in the design,
// and trace back from every AstSenItem through all logic that might (via an
// Active region update) feed into triggering that AstSenItem. Any such logic
// is then assigned to the 'act' region, and all other logic is assigned to
// the 'nba' region.
//
// For later practical purposes, AstAssignPre logic that would be assigned to
// the 'act' region is returned separately. Nevertheless, this logic is part of
// the 'act' region.
//
// For more details, please see the internals documentation.
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Error.h"
#include "V3Global.h"
#include "V3Sched.h"
#include "V3Graph.h"
#include "V3EmitV.h"
#include <tuple>
#include <unordered_map>
#include <vector>
namespace V3Sched {
namespace {
class SchedSenVertex final : public V3GraphVertex {
const AstSenItem* const m_senItemp;
public:
SchedSenVertex(V3Graph* graphp, const AstSenItem* senItemp)
: V3GraphVertex{graphp}
, m_senItemp{senItemp} {}
string name() const override {
std::ostringstream os;
V3EmitV::verilogForTree(const_cast<AstSenItem*>(m_senItemp), os);
return os.str();
}
string dotShape() const override { return "doubleoctagon"; }
string dotColor() const override { return "red"; }
};
class SchedLogicVertex final : public V3GraphVertex {
AstScope* const m_scopep;
AstSenTree* const m_senTreep;
AstNode* const m_logicp;
public:
SchedLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep, AstNode* logicp)
: V3GraphVertex{graphp}
, m_scopep{scopep}
, m_senTreep{senTreep}
, m_logicp{logicp} {}
AstScope* scopep() const { return m_scopep; }
AstSenTree* senTreep() const { return m_senTreep; }
AstNode* logicp() const { return m_logicp; }
string name() const override {
return m_logicp->typeName() + ("\n" + m_logicp->fileline()->ascii());
};
string dotShape() const override { return "rectangle"; }
};
class SchedVarVertex final : public V3GraphVertex {
const AstVarScope* const m_vscp;
public:
SchedVarVertex(V3Graph* graphp, AstVarScope* vscp)
: V3GraphVertex{graphp}
, m_vscp{vscp} {}
string name() const override { return m_vscp->name(); }
string dotShape() const override {
return m_vscp->scopep()->isTop() && m_vscp->varp()->isNonOutput() ? "invhouse" : "ellipse";
}
string dotColor() const override {
return m_vscp->scopep()->isTop() && m_vscp->varp()->isNonOutput() ? "green" : "black";
}
};
class SchedGraphBuilder final : public VNVisitor {
// NODE STATE
// AstVarScope::user1() -> SchedVarVertex
// AstSenItem::user1p() -> SchedSenVertex
// AstVarScope::user2() -> bool: Read of this AstVarScope triggers this logic.
// Used only for hybrid logic.
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
// STATE
V3Graph* const m_graphp = new V3Graph; // The dataflow graph being built
// The vertices associated with a unique AstSenItem
std::unordered_map<VNRef<AstSenItem>, SchedSenVertex*> m_senVertices;
AstScope* m_scopep = nullptr; // AstScope of the current AstActive
AstSenTree* m_senTreep = nullptr; // AstSenTree of the current AstActive
// Predicate for whether a read of the given variable triggers this block
std::function<bool(AstVarScope*)> m_readTriggersThisLogic;
VL_DEBUG_FUNC;
SchedVarVertex* getVarVertex(AstVarScope* vscp) const {
if (!vscp->user1p()) vscp->user1p(new SchedVarVertex{m_graphp, vscp});
return vscp->user1u().to<SchedVarVertex*>();
}
SchedSenVertex* getSenVertex(AstSenItem* senItemp) {
if (!senItemp->user1p()) {
// There is a unique SchedSenVertex for each globally unique AstSenItem. Multiple
// AstSenTree might use the same AstSenItem (e.g.: posedge clk1 or rst, posedge clk2 or
// rst), so we use a hash map to get the unique SchedSenVertex. (Note: This creates
// separate vertices for ET_CHANGED and ET_HYBRID over the same expression, but that is
// OK for now).
auto it = m_senVertices.find(*senItemp);
// If it does not exist, create it
if (it == m_senVertices.end()) {
// Create the vertex
SchedSenVertex* const vtxp = new SchedSenVertex{m_graphp, senItemp};
// Connect up the variable references
senItemp->sensp()->foreach<AstVarRef>([&](AstVarRef* refp) {
SchedVarVertex* const varVtxp = getVarVertex(refp->varScopep());
new V3GraphEdge{m_graphp, varVtxp, vtxp, 1};
});
// Store back to hash map so we can find it next time
it = m_senVertices.emplace(*senItemp, vtxp).first;
}
// Cache sensitivity vertex
senItemp->user1p(it->second);
}
return senItemp->user1u().to<SchedSenVertex*>();
}
void visitLogic(AstNode* nodep) {
UASSERT_OBJ(m_senTreep, nodep, "Should be under AstActive");
SchedLogicVertex* const logicVtxp
= new SchedLogicVertex{m_graphp, m_scopep, m_senTreep, nodep};
// Clocked or hybrid logic has explicit sensitivity, so add edge from sensitivity vertex
if (!m_senTreep->hasCombo()) {
m_senTreep->foreach<AstSenItem>([=](AstSenItem* senItemp) {
if (senItemp->isIllegal()) return;
UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), nodep,
"Non-clocked SenItem under clocked SenTree");
V3GraphVertex* const eventVtxp = getSenVertex(senItemp);
new V3GraphEdge{m_graphp, eventVtxp, logicVtxp, 10};
});
}
// Add edges based on references
nodep->foreach<AstVarRef>([=](const AstVarRef* vrefp) {
AstVarScope* const vscp = vrefp->varScopep();
SchedVarVertex* const varVtxp = getVarVertex(vscp);
if (vrefp->access().isReadOrRW() && m_readTriggersThisLogic(vscp)) {
new V3GraphEdge{m_graphp, varVtxp, logicVtxp, 10};
}
if (vrefp->access().isWriteOrRW()) {
new V3GraphEdge{m_graphp, logicVtxp, varVtxp, 10};
}
});
// If the logic calls a DPI import, it might fire the DPI Export trigger
if (AstVarScope* const dpiExporTrigger = v3Global.rootp()->dpiExportTriggerp()) {
nodep->foreach<AstCCall>([=](const AstCCall* callp) {
if (!callp->funcp()->dpiImportWrapper()) return;
SchedVarVertex* const varVtxp = getVarVertex(dpiExporTrigger);
new V3GraphEdge{m_graphp, logicVtxp, varVtxp, 10};
});
}
}
// VISIT methods
virtual void visit(AstActive* nodep) override {
AstSenTree* const senTreep = nodep->sensesp();
UASSERT_OBJ(senTreep->hasClocked() || senTreep->hasCombo() || senTreep->hasHybrid(), nodep,
"Unhandled");
UASSERT_OBJ(!m_senTreep, nodep, "Should not nest");
// Mark explicit sensitivities as not triggering these blocks
if (senTreep->hasHybrid()) {
AstNode::user2ClearTree();
senTreep->foreach<AstVarRef>([](const AstVarRef* refp) { //
refp->varScopep()->user2(true);
});
}
m_senTreep = senTreep;
iterateChildrenConst(nodep);
m_senTreep = nullptr;
}
virtual void visit(AstNodeProcedure* nodep) override { visitLogic(nodep); }
virtual void visit(AstNodeAssign* nodep) override { visitLogic(nodep); }
virtual void visit(AstCoverToggle* nodep) override { visitLogic(nodep); }
virtual void visit(AstAlwaysPublic* nodep) override { visitLogic(nodep); }
// Pre and Post logic are handled separately
virtual void visit(AstAssignPre* nodep) override {}
virtual void visit(AstAssignPost* nodep) override {}
virtual void visit(AstAlwaysPost* nodep) override {}
// Ignore
virtual void visit(AstInitialStatic* nodep) override { // LCOV_EXCL_START
nodep->v3fatalSrc("Should not need ordering");
}
virtual void visit(AstInitial* nodep) override {
nodep->v3fatalSrc("Should not need ordering");
}
virtual void visit(AstFinal* nodep) override {
nodep->v3fatalSrc("Should not need ordering");
} // LCOV_EXCL_STOP
virtual void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
SchedGraphBuilder(const LogicByScope& clockedLogic, const LogicByScope& combinationalLogic,
const LogicByScope& hybridLogic) {
// Build the data flow graph
const auto iter = [this](const LogicByScope& lbs) {
for (const auto& pair : lbs) {
m_scopep = pair.first;
iterate(pair.second);
m_scopep = nullptr;
}
};
// Clocked logic is never triggered by reads
m_readTriggersThisLogic = [](AstVarScope*) { return false; };
iter(clockedLogic);
// Combinational logic is always triggered by reads
m_readTriggersThisLogic = [](AstVarScope*) { return true; };
iter(combinationalLogic);
// Hybrid logic is triggered by all reads, except for reads of the explicit sensitivities
m_readTriggersThisLogic = [](AstVarScope* vscp) { return !vscp->user2(); };
iter(hybridLogic);
}
public:
// Build the dataflow graph for partitioning
static std::unique_ptr<V3Graph> build(const LogicByScope& clockedLogic,
const LogicByScope& combinationalLogic,
const LogicByScope& hybridLogic) {
SchedGraphBuilder visitor{clockedLogic, combinationalLogic, hybridLogic};
return std::unique_ptr<V3Graph>{visitor.m_graphp};
}
};
void colorActiveRegion(const V3Graph& graph) {
// Work queue for depth first traversal
std::vector<V3GraphVertex*> queue{};
// Trace from all SchedSenVertex
for (V3GraphVertex* vtxp = graph.verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
if (const auto activeEventVtxp = dynamic_cast<SchedSenVertex*>(vtxp)) {
queue.push_back(activeEventVtxp);
}
}
// Depth first traversal
while (!queue.empty()) {
// Pop next work item
V3GraphVertex& vtx = *queue.back();
queue.pop_back();
// If not first encounter, move on
if (vtx.color() != 0) continue;
// Mark vertex as being in active region
vtx.color(1);
// Enqueue all parent vertices that feed this vertex.
for (V3GraphEdge* edgep = vtx.inBeginp(); edgep; edgep = edgep->inNextp()) {
queue.push_back(edgep->fromp());
}
// If this is a logic vertex, also enqueue all variable vertices that are driven from this
// logic. This will ensure that if a variable is set in the active region, then all
// settings of that variable will be in the active region.
if (dynamic_cast<SchedLogicVertex*>(&vtx)) {
for (V3GraphEdge* edgep = vtx.outBeginp(); edgep; edgep = edgep->outNextp()) {
UASSERT(dynamic_cast<SchedVarVertex*>(edgep->top()), "Should be var vertex");
queue.push_back(edgep->top());
}
}
}
}
} // namespace
LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
LogicByScope& hybridLogic) {
UINFO(2, __FUNCTION__ << ": " << endl);
// Build the graph
const std::unique_ptr<V3Graph> graphp
= SchedGraphBuilder::build(clockedLogic, combinationalLogic, hybridLogic);
if (debug() > 6) graphp->dumpDotFilePrefixed("sched");
// Partition into Active and NBA regions
colorActiveRegion(*(graphp.get()));
if (debug() > 6) graphp->dumpDotFilePrefixed("sched-partitioned", true);
LogicRegions result;
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
if (const auto lvtxp = dynamic_cast<SchedLogicVertex*>(vtxp)) {
LogicByScope& lbs = lvtxp->color() ? result.m_act : result.m_nba;
AstNode* const logicp = lvtxp->logicp();
logicp->unlinkFrBack();
lbs.add(lvtxp->scopep(), lvtxp->senTreep(), logicp);
}
}
// Partition the Pre logic
{
const VNUser1InUse user1InUse; // AstVarScope::user1() -> bool: read in Active region
const VNUser2InUse user2InUse; // AstVarScope::user2() -> bool: writen in Active region
const auto markVars = [](AstNode* nodep) {
nodep->foreach<AstNodeVarRef>([](const AstNodeVarRef* vrefp) {
AstVarScope* const vscp = vrefp->varScopep();
if (vrefp->access().isReadOrRW()) vscp->user1(true);
if (vrefp->access().isWriteOrRW()) vscp->user2(true);
});
};
for (const auto& pair : result.m_act) {
AstActive* const activep = pair.second;
markVars(activep->sensesp());
markVars(activep);
}
// AstAssignPre, AstAssignPost and AstAlwaysPost should only appear under a clocked
// AstActive, and should be the only thing left at this point.
for (const auto& pair : clockedLogic) {
AstScope* const scopep = pair.first;
AstActive* const activep = pair.second;
for (AstNode *nodep = activep->stmtsp(), *nextp; nodep; nodep = nextp) {
nextp = nodep->nextp();
if (AstAssignPre* const logicp = VN_CAST(nodep, AssignPre)) {
bool toActiveRegion = false;
logicp->foreach<AstNodeVarRef>([&](const AstNodeVarRef* vrefp) {
AstVarScope* const vscp = vrefp->varScopep();
if (vrefp->access().isReadOnly()) {
// Variable only read in Pre, and is written in active region
if (vscp->user2()) toActiveRegion = true;
} else {
// Variable written in Pre, and referenced in active region
if (vscp->user1() || vscp->user2()) toActiveRegion = true;
}
});
LogicByScope& lbs = toActiveRegion ? result.m_pre : result.m_nba;
logicp->unlinkFrBack();
lbs.add(scopep, activep->sensesp(), logicp);
} else {
UASSERT_OBJ(VN_IS(nodep, AssignPost) || VN_IS(nodep, AlwaysPost), nodep,
"Unexpected node type " << nodep->typeName());
nodep->unlinkFrBack();
result.m_nba.add(scopep, activep->sensesp(), nodep);
}
}
}
}
// Clean up remains of inputs
clockedLogic.deleteActives();
combinationalLogic.deleteActives();
hybridLogic.deleteActives();
return result;
}
} // namespace V3Sched

268
src/V3SchedReplicate.cpp Normal file
View File

@ -0,0 +1,268 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Scheduling - replicate combinational logic
//
// 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
//
//*************************************************************************
//
// Combinational (including hybrid) logic driven from both the 'act' and 'nba'
// region needs to be re-evaluated even if only one of those regions updates
// an input variable. We achieve this by replicating such combinational logic
// in both the 'act' and 'nba' regions.
//
// Furthermore we also replicate all combinational logic driven from a top
// level input into a separate 'ico' (Input Combinational) region which is
// executed at the beginning of the time step. This allows us to change both
// data and clock signals during the same 'eval' call while maintaining the
// combinational invariant required by V3Order.
//
// The implementation is a simple graph algorithm, where we build a dependency
// graph of all logic in the design, and then propagate the driving region
// information through it. We then replicate any logic into its additional
// driving regions.
//
// For more details, please see the internals documentation.
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Error.h"
#include "V3Sched.h"
#include "V3Graph.h"
#include <vector>
namespace V3Sched {
namespace {
// Driving region flags
enum RegionFlags : uint8_t {
NONE = 0x0, //
INPUT = 0x1, // Variable/logic is driven from top level input
ACTIVE = 0x2, // Variable/logic is driven from 'act' region logic
NBA = 0x4 // Variable/logic is driven from 'nba' region logic
};
//##############################################################################
// Data structures (graph types)
class Vertex VL_NOT_FINAL : public V3GraphVertex {
RegionFlags m_drivingRegions{NONE}; // The regions driving this vertex
public:
Vertex(V3Graph* graphp)
: V3GraphVertex{graphp} {}
uint8_t drivingRegions() const { return m_drivingRegions; }
void addDrivingRegions(uint8_t regions) {
m_drivingRegions = static_cast<RegionFlags>(m_drivingRegions | regions);
}
// For graph dumping
string dotColor() const override {
switch (static_cast<unsigned>(m_drivingRegions)) {
case NONE: return "black";
case INPUT: return "red";
case ACTIVE: return "green";
case NBA: return "blue";
case INPUT | ACTIVE: return "yellow";
case INPUT | NBA: return "magenta";
case ACTIVE | NBA: return "cyan";
case INPUT | ACTIVE | NBA: return "gray80"; // don't want white on white background
default: v3fatal("There are only 3 region bits"); return ""; // LCOV_EXCL_LINE
}
}
};
class LogicVertex final : public Vertex {
AstScope* const m_scopep; // The enclosing AstScope of the logic node
AstSenTree* const m_senTreep; // The sensitivity of the logic node
AstNode* const m_logicp; // The logic node this vertex represents
RegionFlags const m_assignedRegion; // The region this logic is originally assigned to
public:
LogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep, AstNode* logicp,
RegionFlags assignedRegion)
: Vertex{graphp}
, m_scopep{scopep}
, m_senTreep{senTreep}
, m_logicp{logicp}
, m_assignedRegion{assignedRegion} {
addDrivingRegions(assignedRegion);
}
AstScope* scopep() const { return m_scopep; }
AstSenTree* senTreep() const { return m_senTreep; }
AstNode* logicp() const { return m_logicp; }
RegionFlags assignedRegion() const { return m_assignedRegion; }
// For graph dumping
string name() const override { return m_logicp->fileline()->ascii(); };
string dotShape() const override { return "rectangle"; }
};
class VarVertex final : public Vertex {
AstVarScope* const m_vscp; // The AstVarScope this vertex represents
public:
VarVertex(V3Graph* graphp, AstVarScope* vscp)
: Vertex{graphp}
, m_vscp{vscp} {
if (isTopInput()) addDrivingRegions(INPUT);
}
AstVarScope* vscp() const { return m_vscp; }
AstVar* varp() const { return m_vscp->varp(); }
AstScope* scopep() const { return m_vscp->scopep(); }
bool isTopInput() const { return scopep()->isTop() && varp()->isNonOutput(); }
// For graph dumping
string name() const override { return m_vscp->name(); }
string dotShape() const override { return isTopInput() ? "invhouse" : "ellipse"; }
};
class Graph final : public V3Graph {};
//##############################################################################
// Algorithm implementation
std::unique_ptr<Graph> buildGraph(const LogicRegions& logicRegions) {
std::unique_ptr<Graph> graphp{new Graph};
// AstVarScope::user1() -> VarVertx
const VNUser1InUse user1InUse;
const auto getVarVertex = [&](AstVarScope* vscp) {
if (!vscp->user1p()) vscp->user1p(new VarVertex{graphp.get(), vscp});
return vscp->user1u().to<VarVertex*>();
};
const auto addEdge = [&](Vertex* fromp, Vertex* top) {
new V3GraphEdge{graphp.get(), fromp, top, 1};
};
const auto addLogic = [&](RegionFlags region, AstScope* scopep, AstActive* activep) {
AstSenTree* const senTreep = activep->sensesp();
// Predicate for whether a read of the given variable triggers this block
std::function<bool(AstVarScope*)> readTriggersThisLogic;
const VNUser4InUse user4InUse; // bool: Explicit sensitivity of hybrid logic just below
if (senTreep->hasClocked()) {
// Clocked logic is never triggered by reads
readTriggersThisLogic = [](AstVarScope*) { return false; };
} else if (senTreep->hasCombo()) {
// Combinational logic is always triggered by reads
readTriggersThisLogic = [](AstVarScope*) { return true; };
} else {
UASSERT_OBJ(senTreep->hasHybrid(), activep, "unexpected");
// Hybrid logic is triggered by all reads, except for reads of the explicit
// sensitivities
readTriggersThisLogic = [](AstVarScope* vscp) { return !vscp->user4(); };
senTreep->foreach<AstVarRef>([](const AstVarRef* refp) { //
refp->varScopep()->user4(true);
});
}
for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
LogicVertex* const lvtxp
= new LogicVertex{graphp.get(), scopep, senTreep, nodep, region};
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
nodep->foreach<AstVarRef>([&](AstVarRef* refp) {
AstVarScope* const vscp = refp->varScopep();
VarVertex* const vvtxp = getVarVertex(vscp);
// If read, add var -> logic edge
// Note: Use same heuristic as ordering does to ignore written variables
// TODO: Use live variable analysis.
if (refp->access().isReadOrRW() && !vscp->user3SetOnce()
&& readTriggersThisLogic(vscp) && !vscp->user2()) { //
addEdge(vvtxp, lvtxp);
}
// If written, add logic -> var edge
// Note: See V3Order for why AlwaysPostponed is safe to be ignored. We ignore it
// as otherwise we would end up with a false cycle.
if (refp->access().isWriteOrRW() && !vscp->user2SetOnce()
&& !VN_IS(nodep, AlwaysPostponed)) { //
addEdge(lvtxp, vvtxp);
}
});
}
};
for (const auto& pair : logicRegions.m_pre) addLogic(ACTIVE, pair.first, pair.second);
for (const auto& pair : logicRegions.m_act) addLogic(ACTIVE, pair.first, pair.second);
for (const auto& pair : logicRegions.m_nba) addLogic(NBA, pair.first, pair.second);
return graphp;
}
void propagateDrivingRegions(Vertex* vtxp) {
// Note: The graph is always acyclic, so the recursion will terminate
// Nothing to do if already visited
if (vtxp->user()) return;
// Compute union of driving regions of all inputs
uint8_t drivingRegions = 0;
for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
Vertex* const srcp = static_cast<Vertex*>(edgep->fromp());
propagateDrivingRegions(srcp);
drivingRegions |= srcp->drivingRegions();
}
// Add any new driving regions
vtxp->addDrivingRegions(drivingRegions);
// Mark as visited
vtxp->user(true);
}
LogicReplicas replicate(Graph* graphp) {
LogicReplicas result;
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
if (LogicVertex* const lvtxp = dynamic_cast<LogicVertex*>(vtxp)) {
const auto replicateTo = [&](LogicByScope& lbs) {
lbs.add(lvtxp->scopep(), lvtxp->senTreep(), lvtxp->logicp()->cloneTree(false));
};
const uint8_t targetRegions = lvtxp->drivingRegions() & ~lvtxp->assignedRegion();
UASSERT(!lvtxp->senTreep()->hasClocked() || targetRegions == 0,
"replicating clocked logic");
if (targetRegions & INPUT) replicateTo(result.m_ico);
if (targetRegions & ACTIVE) replicateTo(result.m_act);
if (targetRegions & NBA) replicateTo(result.m_nba);
}
}
return result;
}
} // namespace
LogicReplicas replicateLogic(LogicRegions& logicRegionsRegions) {
// Build the dataflow (dependency) graph
const std::unique_ptr<Graph> graphp = buildGraph(logicRegionsRegions);
// Dump for debug
graphp->dumpDotFilePrefixed("sched-replicate");
// Propagate driving region flags
for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) {
propagateDrivingRegions(static_cast<Vertex*>(vtxp));
}
// Dump for debug
graphp->dumpDotFilePrefixed("sched-replicate-propagated");
// Replicate the necessary logic
return replicate(graphp.get());
}
} // namespace V3Sched

View File

@ -36,9 +36,20 @@ private:
// STATE
AstTopScope* const m_topScopep; // Top scope to add global SenTrees to
std::unordered_set<VNRef<AstSenTree>> m_trees; // Set of global SenTrees
AstSenTree* m_combop = nullptr; // The unique combinational domain SenTree
AstSenTree* m_initialp = nullptr; // The unique initial domain SenTree
VL_UNCOPYABLE(SenTreeFinder);
template <typename T_Domain> //
AstSenTree* makeUnique() {
FileLine* const fl = m_topScopep->fileline();
AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, T_Domain{}}};
AstSenTree* const restultp = getSenTree(senTreep);
VL_DO_DANGLING(senTreep->deleteTree(), senTreep); // getSenTree clones, so can delete
return restultp;
}
public:
// CONSTRUCTORS
SenTreeFinder()
@ -50,6 +61,8 @@ public:
for (AstSenTree* senTreep = m_topScopep->senTreesp(); senTreep;
senTreep = VN_AS(senTreep->nextp(), SenTree)) {
m_trees.emplace(*senTreep);
if (senTreep->hasCombo()) m_combop = senTreep;
if (senTreep->hasInitial()) m_initialp = senTreep;
}
}
@ -72,11 +85,15 @@ public:
// Return the global combinational AstSenTree.
// If no such global SenTree exists create one and add it to the stored AstTopScope.
AstSenTree* getComb() {
FileLine* const fl = m_topScopep->fileline();
AstSenTree* const combp = new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Combo()}};
AstSenTree* const resultp = getSenTree(combp);
VL_DO_DANGLING(combp->deleteTree(), combp); // getSenTree clones, so can delete
return resultp;
if (!m_combop) m_combop = makeUnique<AstSenItem::Combo>();
return m_combop;
}
// Return the global initial AstSenTree.
// If no such global SenTree exists create one and add it to the stored AstTopScope.
AstSenTree* getInitial() {
if (!m_initialp) m_initialp = makeUnique<AstSenItem::Initial>();
return m_initialp;
}
};

View File

@ -1056,17 +1056,18 @@ private:
}
AstVarScope* makeDpiExporTrigger() {
AstVarScope* dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp();
AstNetlist* const netlistp = v3Global.rootp();
AstVarScope* dpiExportTriggerp = netlistp->dpiExportTriggerp();
if (!dpiExportTriggerp) {
// Create the global DPI export trigger flag the first time we encounter a DPI export.
// This flag is set any time a DPI export is invoked, and cleared at the end of eval.
FileLine* const fl = m_topScopep->fileline();
AstVar* const varp
= new AstVar{fl, VVarType::VAR, "__Vdpi_export_trigger", VFlagBitPacked{}, 1};
const string name{"__Vdpi_export_trigger"};
AstVar* const varp = new AstVar{fl, VVarType::VAR, name, VFlagBitPacked{}, 1};
m_topScopep->scopep()->modp()->addStmtp(varp);
dpiExportTriggerp = new AstVarScope{fl, m_topScopep->scopep(), varp};
m_topScopep->scopep()->addVarp(dpiExportTriggerp);
v3Global.rootp()->dpiExportTriggerp(dpiExportTriggerp);
netlistp->dpiExportTriggerp(dpiExportTriggerp);
}
return dpiExportTriggerp;
}
@ -1309,7 +1310,7 @@ private:
AstAlways* const alwaysp = new AstAlways{
fl, VAlwaysKwd::ALWAYS,
new AstSenTree{
fl, new AstSenItem{fl, VEdgeType::ET_HIGHEDGE,
fl, new AstSenItem{fl, VEdgeType::ET_DPIEXPORT,
new AstVarRef{fl, dpiExportTriggerp, VAccess::READ}}},
nullptr};
for (AstVarScope* const varScopep : writtenps) {

View File

@ -2519,7 +2519,7 @@ private:
methodCallClass(nodep, adtypep);
} else if (AstUnpackArrayDType* const adtypep = VN_CAST(fromDtp, UnpackArrayDType)) {
methodCallUnpack(nodep, adtypep);
} else if (basicp && basicp->isEventValue()) {
} else if (basicp && basicp->isEvent()) {
methodCallEvent(nodep, basicp);
} else if (basicp && basicp->isString()) {
methodCallString(nodep, basicp);
@ -3119,10 +3119,12 @@ private:
void methodCallEvent(AstMethodCall* nodep, AstBasicDType*) {
// Method call on event
if (nodep->name() == "triggered") {
// We represent events as numbers, so can just return number
methodOkArguments(nodep, 0, 0);
AstNode* const newp = nodep->fromp()->unlinkFrBack();
nodep->replaceWith(newp);
AstCMethodHard* const callp = new AstCMethodHard{
nodep->fileline(), nodep->fromp()->unlinkFrBack(), "isTriggered"};
callp->dtypeSetBit();
callp->pure(true);
nodep->replaceWith(callp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
nodep->v3error("Unknown built-in event method " << nodep->prettyNameQ());
@ -3979,7 +3981,7 @@ private:
// if (debug()) nodep->dumpTree(cout, " AssignOut: ");
}
if (const AstBasicDType* const basicp = nodep->rhsp()->dtypep()->basicp()) {
if (basicp->isEventValue()) {
if (basicp->isEvent()) {
// see t_event_copy.v for commentary on the mess involved
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type");
}
@ -4783,6 +4785,9 @@ private:
userIterateChildren(nodep, nullptr);
m_procedurep = nullptr;
}
virtual void visit(AstSenItem* nodep) override {
userIterateChildren(nodep, WidthVP(SELF, BOTH).p());
}
virtual void visit(AstWith* nodep) override {
// Should otherwise be underneath a method call
AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp();

View File

@ -29,7 +29,6 @@
#include "V3Case.h"
#include "V3Cast.h"
#include "V3Cdc.h"
#include "V3Changed.h"
#include "V3Class.h"
#include "V3Clean.h"
#include "V3Clock.h"
@ -53,7 +52,6 @@
#include "V3File.h"
#include "V3Force.h"
#include "V3Gate.h"
#include "V3GenClk.h"
#include "V3Graph.h"
#include "V3HierBlock.h"
#include "V3Inline.h"
@ -70,7 +68,6 @@
#include "V3Localize.h"
#include "V3MergeCond.h"
#include "V3Name.h"
#include "V3Order.h"
#include "V3Os.h"
#include "V3Param.h"
#include "V3ParseSym.h"
@ -80,6 +77,7 @@
#include "V3ProtectLib.h"
#include "V3Randomize.h"
#include "V3Reloop.h"
#include "V3Sched.h"
#include "V3Scope.h"
#include "V3Scoreboard.h"
#include "V3Slice.h"
@ -376,11 +374,8 @@ static void process() {
if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "PreOrder");
// Order the code; form SBLOCKs and BLOCKCALLs
V3Order::orderAll(v3Global.rootp());
// Change generated clocks to look at delayed signals
V3GenClk::genClkAll(v3Global.rootp());
// Schedule the logic
V3Sched::schedule(v3Global.rootp());
// Convert sense lists into IF statements.
V3Clock::clockAll(v3Global.rootp());
@ -392,15 +387,13 @@ static void process() {
V3Const::constifyAll(v3Global.rootp());
V3Life::lifeAll(v3Global.rootp());
}
if (v3Global.opt.oLifePost()) V3LifePost::lifepostAll(v3Global.rootp());
// Remove unused vars
V3Const::constifyAll(v3Global.rootp());
V3Dead::deadifyAllScoped(v3Global.rootp());
// Detect change loop
V3Changed::changedAll(v3Global.rootp());
// Create tracing logic, since we ripped out some signals the user might want to trace
// Note past this point, we presume traced variables won't move between CFuncs
// (It's OK if untraced temporaries move around, or vars

View File

@ -1882,9 +1882,12 @@ data_typeNoRef<nodeDTypep>: // ==IEEE: data_type, excluding class_ty
{ $$ = new AstDefImplicitDType{$1->fileline(),
"__typeimpenum" + cvtToStr(GRAMMARP->s_modTypeImpNum++),
SYMP, VFlagChildDType{}, $1}; }
| ySTRING { $$ = new AstBasicDType($1,VBasicDTypeKwd::STRING); }
| yCHANDLE { $$ = new AstBasicDType($1,VBasicDTypeKwd::CHANDLE); }
| yEVENT { $$ = new AstBasicDType($1,VBasicDTypeKwd::EVENTVALUE); }
| ySTRING
{ $$ = new AstBasicDType{$1, VBasicDTypeKwd::STRING}; }
| yCHANDLE
{ $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; }
| yEVENT
{ $$ = new AstBasicDType{$1, VBasicDTypeKwd::EVENT}; v3Global.setHasEvents(); }
// // Rules overlap virtual_interface_declaration
// // Parameters here are SV2009
// // IEEE has ['.' modport] but that will conflict with port
@ -2999,7 +3002,7 @@ senitem<senItemp>: // IEEE: part of event_expression, non-'OR' ','
;
senitemVar<senItemp>:
idClassSel { $$ = new AstSenItem($1->fileline(), VEdgeType::ET_ANYEDGE, $1); }
idClassSel { $$ = new AstSenItem{$1->fileline(), VEdgeType::ET_CHANGED, $1}; }
;
senitemEdge<senItemp>: // IEEE: part of event_expression
@ -3221,13 +3224,11 @@ statement_item<nodep>: // IEEE: statement_item
| yDISABLE yFORK ';' { $$ = new AstDisableFork($1); }
// // IEEE: event_trigger
| yP_MINUSGT idDotted/*hierarchical_identifier-event*/ ';'
{ // AssignDly because we don't have stratified queue, and need to
// read events, clear next event, THEN apply this set
$$ = new AstAssignDly($1, $2, new AstConst($1, AstConst::BitTrue())); }
{ $$ = new AstFireEvent{$1, $2, false}; }
//UNSUP yP_MINUSGTGT delay_or_event_controlE hierarchical_identifier/*event*/ ';' { UNSUP }
// // IEEE remove below
| yP_MINUSGTGT delayE idDotted/*hierarchical_identifier-event*/ ';'
{ $$ = new AstAssignDly($1, $3, new AstConst($1, AstConst::BitTrue())); }
{ $$ = new AstFireEvent{$1, $3, true}; }
//
// // IEEE: loop_statement
| yFOREVER stmtBlock { $$ = new AstWhile($1,new AstConst($1, AstConst::BitTrue()), $2); }

View File

@ -11,7 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(simulator => 1);
compile(
verilator_flags2 => ["-Wno-CLKDATA"],
);
execute(

View File

@ -16,7 +16,7 @@ compile(
if ($Self->{vlt_all}) {
file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 10);
file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 8);
file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 0);
}
execute(

View File

@ -1,127 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2005 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t (clk);
input clk;
reg [0:0] d1;
reg [2:0] d3;
reg [7:0] d8;
wire [0:0] q1;
wire [2:0] q3;
wire [7:0] q8;
// verilator lint_off UNOPTFLAT
reg ena;
// verilator lint_on UNOPTFLAT
condff #(12) condff
(.clk(clk), .sen(1'b0), .ena(ena),
.d({d8,d3,d1}),
.q({q8,q3,q1}));
integer cyc; initial cyc=1;
always @ (posedge clk) begin
if (cyc!=0) begin
//$write("%x %x %x %x\n", cyc, q8, q3, q1);
cyc <= cyc + 1;
if (cyc==1) begin
d1 <= 1'b1; d3<=3'h1; d8<=8'h11;
ena <= 1'b1;
end
if (cyc==2) begin
d1 <= 1'b0; d3<=3'h2; d8<=8'h33;
ena <= 1'b0;
end
if (cyc==3) begin
d1 <= 1'b1; d3<=3'h3; d8<=8'h44;
ena <= 1'b1;
if (q8 != 8'h11) $stop;
end
if (cyc==4) begin
d1 <= 1'b1; d3<=3'h4; d8<=8'h77;
ena <= 1'b1;
if (q8 != 8'h11) $stop;
end
if (cyc==5) begin
d1 <= 1'b1; d3<=3'h0; d8<=8'h88;
ena <= 1'b1;
if (q8 != 8'h44) $stop;
end
if (cyc==6) begin
if (q8 != 8'h77) $stop;
end
if (cyc==7) begin
if (q8 != 8'h88) $stop;
end
//
if (cyc==20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
end
endmodule
module condff (clk, sen, ena, d, q);
parameter WIDTH = 1;
input clk;
input sen;
input ena;
input [WIDTH-1:0] d;
output [WIDTH-1:0] q;
condffimp #(.WIDTH(WIDTH))
imp (.clk(clk), .sen(sen), .ena(ena), .d(d), .q(q));
endmodule
module condffimp (clk, sen, ena, d, q);
parameter WIDTH = 1;
input clk;
input sen;
input ena;
input [WIDTH-1:0] d;
output reg [WIDTH-1:0] q;
wire gatedclk;
clockgate clockgate (.clk(clk), .sen(sen), .ena(ena), .gatedclk(gatedclk));
always @(posedge gatedclk) begin
if (gatedclk === 1'bX) begin
q <= {WIDTH{1'bX}};
end
else begin
q <= d;
end
end
endmodule
module clockgate (clk, sen, ena, gatedclk);
input clk;
input sen;
input ena;
output gatedclk;
reg ena_b;
wire gatedclk = clk & ena_b;
// verilator lint_off COMBDLY
// verilator lint_off LATCH
always @(clk or ena or sen) begin
if (~clk) begin
ena_b <= ena | sen;
end
else begin
if ((clk^sen)===1'bX) ena_b <= 1'bX;
end
end
// verilator lint_on LATCH
// verilator lint_on COMBDLY
endmodule

View File

@ -11,8 +11,6 @@ module t (/*AUTOARG*/
input clk;
// verilator lint_off GENCLK
reg [7:0] cyc; initial cyc = 0;
reg genclk;
// verilator lint_off MULTIDRIVEN

View File

@ -11,8 +11,6 @@ module t (/*AUTOARG*/
input clk;
// verilator lint_off GENCLK
reg [7:0] cyc; initial cyc = 0;
reg [7:0] padd;
reg dsp_ph1, dsp_ph2, dsp_reset;

View File

@ -52,10 +52,8 @@ module t_clk (/*AUTOARG*/
// verilator lint_on MULTIDRIVEN
reg [7:0] int_clocks_copy;
// verilator lint_off GENCLK
reg internal_clk; initial internal_clk = 0;
reg reset_int_;
// verilator lint_on GENCLK
always @ (posedge clk) begin
`ifdef TEST_VERBOSE
@ -170,9 +168,7 @@ module t_clk_two (/*AUTOARG*/
);
input fastclk;
input reset_l;
// verilator lint_off GENCLK
reg clk2;
// verilator lint_on GENCLK
reg [31:0] count;
t_clk_twob tb (.*);

View File

@ -12,7 +12,6 @@ module t (/*AUTOARG*/
input clk;
integer cyc; initial cyc=1;
// verilator lint_off GENCLK
reg gendlyclk_r;
reg [31:0] gendlydata_r;
reg [31:0] dlydata_gr;

View File

@ -10,14 +10,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(simulator => 1);
my $fail = $Self->{vlt_all};
compile(
);
execute(
check_finished => !$fail,
fails => $fail,
check_finished => 1
);
ok(1);

View File

@ -12,16 +12,14 @@ scenarios(simulator => 1);
top_filename("t/t_clk_latch.v");
my $fail = $Self->{vlt_all};
compile(
v_flags2 => ['+define+EDGE_DETECT_STYLE'],
fails => $fail,
);
execute(
check_finished => !$fail,
) if !$fail;
check_finished => 1
);
ok(1);
1;

View File

@ -19,7 +19,6 @@
`define GATED_CLK_TESTCASE 1
// A side effect of the problem is this warning, disabled by default
//verilator lint_on IMPERFECTSCH
// Test Bench
module t (/*AUTOARG*/
@ -153,7 +152,7 @@ module Test (/*AUTOARG*/
output wire [7:0] entry_vld;
wire [7:0] gclk_vld;
wire [7:0] ff_en_vld /*verilator clock_enable*/;
wire [7:0] ff_en_vld;
reg [7:0] flop_en_vld;
always @(posedge clk) flop_en_vld <= ff_en_e1;

View File

@ -13,8 +13,6 @@ module t (/*AUTOARG*/
reg reset_l;
// verilator lint_off GENCLK
/*AUTOWIRE*/
// Beginning of automatic wires (for undeclared instantiated-module outputs)
// End of automatics

View File

@ -1,7 +0,0 @@
%Warning-CLKDATA: t/t_clk_scope_bad.v:36:12: Clock used as data (on rhs of assignment) in sequential block 'clk'
: ... In instance t.p2
36 | q <= d;
| ^
... For warning description see https://verilator.org/warn/CLKDATA?v=latest
... Use "/* verilator lint_off CLKDATA */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -10,10 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(linter => 1);
lint(
fails => 1,
expect_filename => $Self->{golden_filename},
);
lint();
ok(1);
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 => ["--trace", "-Wno-CLKDATA"]
verilator_flags2 => ["--trace"]
);
execute(

View File

@ -39,9 +39,6 @@ module t (/*AUTOARG*/
assign clk_3 = {3{clk_1}};
assign clk_final = clk_3[0];
// the following two assignment triggers the CLKDATA warning
// because on LHS there are a mix of signals both CLOCK and
// DATA
assign res8 = {clk_3, 1'b0, clk_4};
assign res16 = {count, clk_3, clk_1, clk_4};
@ -52,8 +49,6 @@ module t (/*AUTOARG*/
always @(posedge clk_final or negedge clk_final) begin
count = count + 1;
// the following assignment should trigger the CLKDATA warning
// because CLOCK signal is used as DATA in sequential block
res <= clk_final;
if ( count == 8'hf) begin
$write("*-* All Finished *-*\n");

View File

@ -1,12 +0,0 @@
%Warning-CLKDATA: t/t_clocker.v:45:17: Clock is assigned to part of data signal 'res8'
45 | assign res8 = {clk_3, 1'b0, clk_4};
| ^
... For warning description see https://verilator.org/warn/CLKDATA?v=latest
... Use "/* verilator lint_off CLKDATA */" and lint_on around source to disable this message.
%Warning-CLKDATA: t/t_clocker.v:46:17: Clock is assigned to part of data signal 'res16'
46 | assign res16 = {count, clk_3, clk_1, clk_4};
| ^
%Warning-CLKDATA: t/t_clocker.v:57:14: Clock used as data (on rhs of assignment) in sequential block 'clk'
57 | res <= clk_final;
| ^~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,40 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Copyright 2022 by Geza Lore. 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
//
//*************************************************************************
#include <memory>
#include "verilated.h"
#include "Vt_comb_input_0.h"
#include "Vt_comb_input_0__Syms.h"
int main(int argc, char** argv, char** env) {
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
contextp->commandArgs(argc, argv);
contextp->debug(0);
srand48(5);
const std::unique_ptr<Vt_comb_input_0> topp{new Vt_comb_input_0};
topp->inc = 1;
topp->clk = false;
topp->eval();
while (!contextp->gotFinish() && contextp->time() < 100000) {
contextp->timeInc(5);
if (topp->clk) topp->inc += 1;
topp->clk = !topp->clk;
topp->eval();
}
if (!contextp->gotFinish()) {
vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish");
}
return 0;
}

View File

@ -0,0 +1,24 @@
#!/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 Geza Lore. 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_all => 1);
compile(
#make_top_shell => 0,
make_main => 0,
v_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"],
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,34 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Copyright 2022 by Geza Lore. 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
module top(
clk,
inc
);
input clk;
input [31:0] inc;
// Cycle count
reg [31:0] cyc = 0;
// Combinational logic driven from primary input
wire [31:0] sum = cyc + inc;
always @(posedge clk) begin
$display("cyc: %d sum: %d", cyc, sum);
if (sum != 2*cyc + 1) $stop;
cyc <= cyc + 1;
if (cyc == 100) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,40 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Copyright 2022 by Geza Lore. 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
//
//*************************************************************************
#include <memory>
#include "verilated.h"
#include "Vt_comb_input_1.h"
#include "Vt_comb_input_1__Syms.h"
int main(int argc, char** argv, char** env) {
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
contextp->commandArgs(argc, argv);
contextp->debug(0);
srand48(5);
const std::unique_ptr<Vt_comb_input_1> topp{new Vt_comb_input_1};
topp->inc = 1;
topp->clk = false;
topp->eval();
while (!contextp->gotFinish() && contextp->time() < 100000) {
contextp->timeInc(5);
if (topp->clk) topp->inc += 1;
topp->clk = !topp->clk;
topp->eval();
}
if (!contextp->gotFinish()) {
vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish");
}
return 0;
}

View File

@ -0,0 +1,24 @@
#!/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 Geza Lore. 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_all => 1);
compile(
#make_top_shell => 0,
make_main => 0,
v_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"],
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,39 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Copyright 2022 by Geza Lore. 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
module top(
clk,
inc
);
input clk;
input [31:0] inc;
// Cycle count
reg [31:0] cyc = 0;
/* verilator lint_off UNOPTFLAT */
// Circular combinational logic driven from primary input. 'msb' is the
// narrowest, so it will be the cut vertex.
wire [31:0] feedback;
wire [31:0] sum = cyc + inc + feedback;
wire msb = sum[31]; // Always 0, but Verilator cannot know that
assign feedback = {32{msb}};
always @(posedge clk) begin
$display("cyc: %d sum: %d", cyc, sum);
if (sum != 2*cyc + 1) $stop;
cyc <= cyc + 1;
if (cyc == 100) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,40 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Copyright 2022 by Geza Lore. 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
//
//*************************************************************************
#include <memory>
#include "verilated.h"
#include "Vt_comb_input_2.h"
#include "Vt_comb_input_2__Syms.h"
int main(int argc, char** argv, char** env) {
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
contextp->commandArgs(argc, argv);
contextp->debug(0);
srand48(5);
const std::unique_ptr<Vt_comb_input_2> topp{new Vt_comb_input_2};
topp->inc = 1;
topp->clk = false;
topp->eval();
while (!contextp->gotFinish() && contextp->time() < 100000) {
contextp->timeInc(5);
if (topp->clk) topp->inc += 1;
topp->clk = !topp->clk;
topp->eval();
}
if (!contextp->gotFinish()) {
vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish");
}
return 0;
}

View File

@ -0,0 +1,24 @@
#!/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 Geza Lore. 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_all => 1);
compile(
#make_top_shell => 0,
make_main => 0,
v_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"],
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,48 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Copyright 2022 by Geza Lore. 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
`ifdef VERILATOR
// The '$c1(1)' is there to prevent inlining of the signal by V3Gate
`define IMPURE_ONE ($c(1))
`else
// Use standard $random (chaces of getting 2 consecutive zeroes is zero).
`define IMPURE_ONE (|($random | $random))
`endif
module top(
clk,
inc
);
input clk;
input [31:0] inc;
// Cycle count
reg [31:0] cyc = 0;
/* verilator lint_off UNOPTFLAT */
// Circular combinational logic driven from primary input, but with the
// cycle itself not involving the primary input
wire [31:0] dup = `IMPURE_ONE ? inc : 32'd0;
wire [31:0] feedback;
wire [31:0] sum = cyc + dup + feedback;
wire msb = sum[31]; // Always 0, but Verilator cannot know that
assign feedback = {32{msb}};
always @(posedge clk) begin
$display("cyc: %d sum: %d", cyc, sum);
if (sum != 2*cyc + 1) $stop;
cyc <= cyc + 1;
if (cyc == 100) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -163,7 +163,7 @@ module Vt_debug_emitv_t;
$display("stmt");
end
end
always @([any] in) begin
always @([changed] in) begin
begin
$display("stmt");
end

View File

@ -13,11 +13,11 @@ scenarios(simulator => 1);
my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml";
compile(
verilator_flags2 => ["--stats $Self->{t_dir}/t_dedupe_clk_gate.vlt"],
verilator_flags2 => ["--stats"],
);
if ($Self->{vlt_all}) {
file_grep("$out_filename", qr/\<var loc="e,44,.*?" name="t.f0.clock_gate.clken_latched" dtype_id="1" vartype="logic" origName="clken_latched" clock_enable="true" latched="true"\/\>/i);
file_grep("$out_filename", qr/\<var loc="\w+,44,.*?" name="t.f0.clock_gate.clken_latched" dtype_id="\d+" vartype="logic" origName="clken_latched" latched="true"\/\>/i);
file_grep($Self->{stats}, qr/Optimizations, Gate sigs deduped\s+(\d+)/i, 4);
}

View File

@ -53,7 +53,7 @@ endmodule
module clock_gate_flop (gated_clk, clk, clken);
output gated_clk;
input clk, clken;
reg clken_r /*verilator clock_enable*/;
reg clken_r;
assign gated_clk = clk & clken_r ;
always @(negedge clk)

View File

@ -1,3 +0,0 @@
`verilator_config
clock_enable -module "clock_gate_latch" -var "clken_latched"

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-CLKDATA", "-Wno-UNOPTFLAT"]
verilator_flags2 => ["-Wno-UNOPTFLAT"]
);
execute(

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-CLKDATA", "-Wno-UNOPTFLAT"]
verilator_flags2 => ["-Wno-UNOPTFLAT"]
);
execute(

View File

@ -33,49 +33,44 @@ module t(/*AUTOARG*/
always @(e2) begin
`WRITE_VERBOSE(("[%0t] e2\n", $time));
if (!e2.triggered) $stop;
last_event[2] = 1;
end
always @(posedge clk) begin
`WRITE_VERBOSE(("[%0t] cyc=%0d last_event=%5b\n", $time, cyc, last_event));
cyc <= cyc + 1;
if (cyc == 1) begin
// Check no initial trigger
if (last_event != 0) $stop;
end
//
else if (cyc == 10) begin
last_event = 0;
-> e1;
end
else if (cyc == 12) begin
if (last_event != 32'b10) $stop;
last_event = 0;
end
else if (cyc == 13) begin
// Check not still triggering
if (last_event != 0) $stop;
last_event = 0;
end
//
else if (cyc == 10) begin
last_event = 0;
->> e2;
end
else if (cyc == 12) begin
if (last_event != 32'b100) $stop;
last_event = 0;
end
else if (cyc == 13) begin
// Check not still triggering
if (last_event != 0) $stop;
last_event = 0;
end
//
else if (cyc == 99) begin
$write("*-* All Finished *-*\n");
$finish;
end
case (cyc)
default: begin
// Check no initial or spurious trigger
if (last_event != 0) $stop;
end
//
10: begin
if (last_event != 0) $stop;
-> e1;
if (!e1.triggered) $stop;
end
11: begin
if (last_event != 32'b10) $stop;
last_event = 0;
end
//
13: begin
if (last_event != 0) $stop;
->> e2;
if (e2.triggered) $stop;
end
14: begin
if (last_event != 32'b100) $stop;
last_event = 0;
end
//
99: begin
$write("*-* All Finished *-*\n");
$finish;
end
endcase
end
endmodule

View File

@ -18,7 +18,8 @@ execute(
check_finished => 1,
);
file_grep(glob_one("$Self->{obj_dir}/Vt_flag_comp_limit_parens___024root__DepSet_*__0__Slow.cpp"), qr/Vdeeptemp/x);
my @files = glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet*__Slow.cpp");
file_grep_any(\@files, qr/Vdeeptemp/i);
ok(1);
1;

View File

@ -15,14 +15,14 @@ sub check_evals {
local $/; undef $/;
my $wholefile = <$fh>;
if ($wholefile =~ /___eval__[0-9]+\(.*\)\s*{/) {
if ($wholefile =~ /__eval_nba__[0-9]+\(.*\)\s*{/) {
++$got;
}
}
$got >= 2 or error("Too few _eval functions found: $got");
}
scenarios(vlt_all => 1);
scenarios(vlt => 1);
compile(
v_flags2 => ["--output-split 1 --output-split-cfuncs 20"],

View File

@ -18,7 +18,8 @@ execute(
check_finished => 1,
);
file_grep(glob_one("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__0__Slow.cpp"), qr/VL_RAND_RESET/);
my @files = glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__Slow.cpp");
file_grep_any(\@files, qr/VL_RAND_RESET/);
ok(1);
1;

View File

@ -56,6 +56,8 @@ module chk (input clk, input rst_l, input expr);
wire noxs = ((expr ^ expr) == 1'b0);
// FIXMEV5: this test is dodgy, noxs can be proven constant, so this block
// should never relly trigger...
reg hasx;
always @ (noxs) begin
if (noxs) begin

View File

@ -4,8 +4,4 @@
%Error: t/t_fuzz_always_bad.v:10:19: Can't find definition of task/function: 'h'
10 | always @ c.a c:h;
| ^
%Error-UNSUPPORTED: t/t_fuzz_always_bad.v:10:14: Unsupported: Complex statement in sensitivity list
10 | always @ c.a c:h;
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -10,6 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(simulator => 1);
# FIXMEV5: fix
skip("This test is racy, needs fixing");
compile(
nc_flags2 => ['+access+r'],
);

View File

@ -10,6 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(simulator => 1);
# FIXMEV5: fix
skip("This test is racy, needs fixing");
compile(
);

View File

@ -1,6 +1,6 @@
created tag with scope = top.t.tag
created tag with scope = top.t.b.gen[0].tag
created tag with scope = top.t.b.gen[1].tag
created tag with scope = top.t.tag
mod a has scope = top.t
mod a has tag = top.t.tag
mod b has scope = top.t.b

View File

@ -34,13 +34,13 @@ module t (/*AUTOARG*/
`ifdef PROTLIB_TOP
secret i_secred(.clk(clk));
`else
/* verilator lint_off UNOPTFLAT */
wire [7:0] out0;
wire [7:0] out1;
wire [7:0] out2;
/* verilator lint_off UNOPTFLAT */
wire [7:0] out3;
wire [7:0] out3_2;
/* verilator lint_on UNOPTFLAT */
wire [7:0] out3_2;
wire [7:0] out5;
wire [7:0] out6;
int count = 0;

View File

@ -12,7 +12,6 @@ module t (/*AUTOARG*/
input clk;
integer cyc; initial cyc=1;
// verilator lint_on GENCLK
reg [31:0] long;
reg [63:0] quad;
wire [31:0] longout;

View File

@ -12,9 +12,9 @@ module t (/*AUTOARG*/
input clk;
integer cyc; initial cyc=1;
// verilator lint_off GENCLK
reg printclk;
// verilator lint_on GENCLK
ps ps (printclk);
reg [7:0] a;

Some files were not shown because too many files have changed in this diff Show More