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:
parent
39b7c47e7b
commit
599d23697d
3
Changes
3
Changes
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
---------------
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
------------------
|
||||
|
||||
|
|
|
@ -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 = "'{";
|
||||
|
|
|
@ -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 \
|
||||
|
|
313
src/V3Active.cpp
313
src/V3Active.cpp
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
130
src/V3Ast.h
130
src/V3Ast.h
|
@ -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 {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
120
src/V3AstNodes.h
120
src/V3AstNodes.h
|
@ -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; }
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
332
src/V3Clock.cpp
332
src/V3Clock.cpp
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(); }
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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\"");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
224
src/V3GenClk.cpp
224
src/V3GenClk.cpp
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
|
|
1352
src/V3Order.cpp
1352
src/V3Order.cpp
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 (.*);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
`verilator_config
|
||||
|
||||
clock_enable -module "clock_gate_latch" -var "clken_latched"
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'],
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue