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:**
|
**Major:**
|
||||||
|
|
||||||
* This is a major new release, currently only in alpha testing.
|
* 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
|
Verilator 4.223 devel
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,6 @@ detailed descriptions of these arguments.
|
||||||
-O3 High performance optimizations
|
-O3 High performance optimizations
|
||||||
-O<optimization-letter> Selectable optimizations
|
-O<optimization-letter> Selectable optimizations
|
||||||
-o <executable> Name of final executable
|
-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.
|
--no-verilate Skip verilation and just compile previously Verilated code.
|
||||||
--output-split <statements> Split .cpp files into pieces
|
--output-split <statements> Split .cpp files into pieces
|
||||||
--output-split-cfuncs <statements> Split model functions
|
--output-split-cfuncs <statements> Split model functions
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
.. comment: generated by t_lint_didnotconverge_bad
|
.. comment: generated by t_lint_didnotconverge_bad
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
-V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request
|
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b)
|
||||||
-V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request_1
|
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge.
|
||||||
-V{t#,#} CHANGE: t/t_lint_didnotconverge_bad.v:14: a
|
|
||||||
%Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.. comment: generated by t_lint_didnotconverge_nodbg_bad
|
.. comment: generated by t_lint_didnotconverge_nodbg_bad
|
||||||
.. code-block::
|
.. 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>
|
.. option:: --clk <signal-name>
|
||||||
|
|
||||||
With :vlopt:`--clk`, the specified signal-name is taken as a root clock
|
With :vlopt:`--clk`, the specified signal is marked as a clock signal.
|
||||||
into the model; Verilator will mark the signal as clocker and
|
|
||||||
propagate the clocker attribute automatically to other signals downstream in
|
|
||||||
that clock tree.
|
|
||||||
|
|
||||||
The provided signal-name is specified using a RTL hierarchy path. For
|
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
|
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
|
individual bits, Verilator will attempt to decompose the vector and
|
||||||
connect the single-bit clock signals.
|
connect the single-bit clock signals.
|
||||||
|
|
||||||
The clocker attribute is useful in cases where Verilator does not
|
In versions prior to 5.002, the clocker attribute is useful in cases where
|
||||||
properly distinguish clock signals from other data signals. Using
|
Verilator does not properly distinguish clock signals from other data
|
||||||
clocker will cause the signal indicated to be considered a clock, and
|
signals. Using clocker will cause the signal indicated to be considered a
|
||||||
remove it from the combinatorial logic reevaluation checking code. This
|
clock, and remove it from the combinatorial logic reevaluation checking
|
||||||
may greatly improve performance.
|
code. This may greatly improve performance.
|
||||||
|
|
||||||
.. option:: --compiler <compiler-name>
|
.. option:: --compiler <compiler-name>
|
||||||
|
|
||||||
|
|
@ -714,6 +711,10 @@ Summary:
|
||||||
|
|
||||||
.. option:: --no-order-clock-delay
|
.. 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
|
Rarely needed. Disables a bug fix for ordering of clock enables with
|
||||||
delayed assignments. This option should only be used when suggested by
|
delayed assignments. This option should only be used when suggested by
|
||||||
the developers.
|
the developers.
|
||||||
|
|
@ -1255,8 +1256,7 @@ Summary:
|
||||||
|
|
||||||
Enable all code style warnings, including code style warnings that are
|
Enable all code style warnings, including code style warnings that are
|
||||||
normally disabled by default. Equivalent to :vlopt:`-Wwarn-lint`
|
normally disabled by default. Equivalent to :vlopt:`-Wwarn-lint`
|
||||||
:vlopt:`-Wwarn-style`. Excludes some specialty warnings,
|
:vlopt:`-Wwarn-style`. Excludes some specialty warnings.
|
||||||
i.e. IMPERFECTSCH.
|
|
||||||
|
|
||||||
.. option:: -Werror-<message>
|
.. option:: -Werror-<message>
|
||||||
|
|
||||||
|
|
@ -1509,6 +1509,10 @@ The grammar of configuration commands is as follows:
|
||||||
|
|
||||||
.. option:: clock_enable -module "<modulename>" -var "<signame>"
|
.. 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
|
Indicate the signal is used to gate a clock, and the user takes
|
||||||
responsibility for insuring there are no races related to it.
|
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*/
|
.. 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
|
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
|
clock, and the user takes responsibility for insuring there are no races
|
||||||
related to it. (Typically by adding a latch, and running static timing
|
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*/
|
.. option:: /*verilator&32;no_clocker*/
|
||||||
|
|
||||||
Specifies that the signal is used as clock or not. This information is
|
Specifies that the signal is used as clock or not. See :vlopt:`--clk`.
|
||||||
used by Verilator to mark the signal and any derived signals as
|
|
||||||
clocker. See :vlopt:`--clk`.
|
|
||||||
|
|
||||||
Same as :option:`clocker` and :option:`no_clocker` in configuration
|
Same as :option:`clocker` and :option:`no_clocker` in configuration
|
||||||
files.
|
files.
|
||||||
|
|
|
||||||
|
|
@ -276,17 +276,6 @@ probably expect, what C does. The default behavior of Verilog is
|
||||||
different.)
|
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
|
Gate Primitives
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
Errors and Warnings
|
Errors and Warnings
|
||||||
*******************
|
*******************
|
||||||
|
|
||||||
|
.. _Disabling Warnings:
|
||||||
|
|
||||||
Disabling Warnings
|
Disabling Warnings
|
||||||
==================
|
==================
|
||||||
|
|
@ -313,20 +314,6 @@ List Of Warnings
|
||||||
potential for reset glitches.
|
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
|
.. option:: CMPCONST
|
||||||
|
|
||||||
.. TODO better example
|
.. TODO better example
|
||||||
|
|
@ -492,7 +479,7 @@ List Of Warnings
|
||||||
passing. Thus to prevent an infinite loop, the Verilated executable
|
passing. Thus to prevent an infinite loop, the Verilated executable
|
||||||
gives the DIDNOTCONVERGE error.
|
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
|
ignored. Though typically it is safe to ignore UNOPTFLAT (at a
|
||||||
performance cost), at the time of issuing a UNOPTFLAT Verilator did not
|
performance cost), at the time of issuing a UNOPTFLAT Verilator did not
|
||||||
know if the logic would eventually converge and assumed it would.
|
know if the logic would eventually converge and assumed it would.
|
||||||
|
|
@ -587,13 +574,6 @@ List Of Warnings
|
||||||
with a newline."
|
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
|
.. option:: HIERBLOCK
|
||||||
|
|
||||||
Warns that the top module is marked as a hierarchy block by the
|
Warns that the top module is marked as a hierarchy block by the
|
||||||
|
|
@ -651,16 +631,6 @@ List Of Warnings
|
||||||
simulate correctly.
|
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
|
.. option:: IMPLICIT
|
||||||
|
|
||||||
.. TODO better example
|
.. TODO better example
|
||||||
|
|
@ -1353,27 +1323,6 @@ List Of Warnings
|
||||||
undriven (...) and will be removed".
|
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
|
.. option:: UNOPTFLAT
|
||||||
|
|
||||||
.. TODO better example
|
.. TODO better example
|
||||||
|
|
@ -1385,10 +1334,6 @@ List Of Warnings
|
||||||
performance; two times better performance may be possible by fixing
|
performance; two times better performance may be possible by fixing
|
||||||
these warnings.
|
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
|
Often UNOPTFLAT is caused by logic that isn't truly circular as viewed by
|
||||||
synthesis which analyzes interconnection per-bit, but is circular to
|
synthesis which analyzes interconnection per-bit, but is circular to
|
||||||
simulation which analyzes per-bus.
|
simulation which analyzes per-bus.
|
||||||
|
|
@ -1441,10 +1386,6 @@ List Of Warnings
|
||||||
the conflict. If you run with `--report-unoptflat` Verilator will
|
the conflict. If you run with `--report-unoptflat` Verilator will
|
||||||
suggest possible candidates for :option:`/*verilator&32;split_var*/`.
|
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
|
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
|
are independent, but occur in the same always block. To fix this, use
|
||||||
the :option:`/*verilator&32;isolate_assignments*/` metacomment described
|
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
|
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 definition (:code:`parameter [31:0]`), or add the
|
||||||
width to the parameter usage (:code:`{PAR[31:0], PAR[31:0]}`).
|
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``.
|
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
|
Multithreaded Mode
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#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_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
|
#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
|
// 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]; }
|
T_Value& operator[](size_t index) { return m_storage[index]; }
|
||||||
const T_Value& operator[](size_t index) const { 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)
|
// Dumping. Verilog: str = $sformatf("%p", assoc)
|
||||||
std::string to_string() const {
|
std::string to_string() const {
|
||||||
std::string out = "'{";
|
std::string out = "'{";
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,6 @@ RAW_OBJS = \
|
||||||
V3Case.o \
|
V3Case.o \
|
||||||
V3Cast.o \
|
V3Cast.o \
|
||||||
V3Cdc.o \
|
V3Cdc.o \
|
||||||
V3Changed.o \
|
|
||||||
V3Class.o \
|
V3Class.o \
|
||||||
V3Clean.o \
|
V3Clean.o \
|
||||||
V3Clock.o \
|
V3Clock.o \
|
||||||
|
|
@ -201,7 +200,6 @@ RAW_OBJS = \
|
||||||
V3FileLine.o \
|
V3FileLine.o \
|
||||||
V3Force.o \
|
V3Force.o \
|
||||||
V3Gate.o \
|
V3Gate.o \
|
||||||
V3GenClk.o \
|
|
||||||
V3Global.o \
|
V3Global.o \
|
||||||
V3Graph.o \
|
V3Graph.o \
|
||||||
V3GraphAlg.o \
|
V3GraphAlg.o \
|
||||||
|
|
@ -239,6 +237,10 @@ RAW_OBJS = \
|
||||||
V3ProtectLib.o \
|
V3ProtectLib.o \
|
||||||
V3Randomize.o \
|
V3Randomize.o \
|
||||||
V3Reloop.o \
|
V3Reloop.o \
|
||||||
|
V3Sched.o \
|
||||||
|
V3SchedAcyclic.o \
|
||||||
|
V3SchedPartition.o \
|
||||||
|
V3SchedReplicate.o \
|
||||||
V3Scope.o \
|
V3Scope.o \
|
||||||
V3Scoreboard.o \
|
V3Scoreboard.o \
|
||||||
V3Slice.o \
|
V3Slice.o \
|
||||||
|
|
|
||||||
313
src/V3Active.cpp
313
src/V3Active.cpp
|
|
@ -206,8 +206,10 @@ class ActiveNamer final : public ActiveBaseVisitor {
|
||||||
private:
|
private:
|
||||||
// STATE
|
// STATE
|
||||||
AstScope* m_scopep = nullptr; // Current scope to add statement to
|
AstScope* m_scopep = nullptr; // Current scope to add statement to
|
||||||
AstActive* m_iActivep = nullptr; // For current scope, the IActive we're building
|
AstActive* m_sActivep = nullptr; // For current scope, the Static active we're building
|
||||||
AstActive* m_cActivep = nullptr; // For current scope, the SActive(combo) 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.
|
// Map from AstSenTree (equivalence) to the corresponding AstActive created.
|
||||||
std::unordered_map<VNRef<AstSenTree>, AstActive*> m_activeMap;
|
std::unordered_map<VNRef<AstSenTree>, AstActive*> m_activeMap;
|
||||||
|
|
@ -217,10 +219,13 @@ private:
|
||||||
UASSERT_OBJ(m_scopep, nodep, "nullptr scope");
|
UASSERT_OBJ(m_scopep, nodep, "nullptr scope");
|
||||||
m_scopep->addActivep(nodep);
|
m_scopep->addActivep(nodep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VISITORS
|
// VISITORS
|
||||||
virtual void visit(AstScope* nodep) override {
|
virtual void visit(AstScope* nodep) override {
|
||||||
m_scopep = nodep;
|
m_scopep = nodep;
|
||||||
|
m_sActivep = nullptr;
|
||||||
m_iActivep = nullptr;
|
m_iActivep = nullptr;
|
||||||
|
m_fActivep = nullptr;
|
||||||
m_cActivep = nullptr;
|
m_cActivep = nullptr;
|
||||||
m_activeMap.clear();
|
m_activeMap.clear();
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
|
|
@ -234,30 +239,28 @@ private:
|
||||||
virtual void visit(AstNodeStmt*) override {} // Accelerate
|
virtual void visit(AstNodeStmt*) override {} // Accelerate
|
||||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||||
|
|
||||||
|
// Specialized below for the special sensitivity classes
|
||||||
|
template <typename SenItemKind> AstActive*& getSpecialActive();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// METHODS
|
// METHODS
|
||||||
AstScope* scopep() { return m_scopep; }
|
AstScope* scopep() { return m_scopep; }
|
||||||
AstActive* getCActive(FileLine* fl) {
|
|
||||||
if (!m_cActivep) {
|
// Return an AstActive sensitive to the given special sensitivity class
|
||||||
m_cActivep = new AstActive(
|
template <typename SenItemKind> AstActive* getSpecialActive(FileLine* fl) {
|
||||||
fl, "combo", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Combo())));
|
AstActive*& cachep = getSpecialActive<SenItemKind>();
|
||||||
m_cActivep->sensesStorep(m_cActivep->sensesp());
|
if (!cachep) {
|
||||||
addActive(m_cActivep);
|
AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}};
|
||||||
|
cachep = new AstActive{fl, "", senTreep};
|
||||||
|
cachep->sensesStorep(cachep->sensesp());
|
||||||
|
addActive(cachep);
|
||||||
}
|
}
|
||||||
return m_cActivep;
|
return cachep;
|
||||||
}
|
|
||||||
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 an AstActive that is sensitive to a SenTree equivalent to the given sentreep.
|
// Return an AstActive that is sensitive to a SenTree equivalent to the given sentreep.
|
||||||
AstActive* getActive(FileLine* fl, AstSenTree* sensesp) {
|
AstActive* getActive(FileLine* fl, AstSenTree* sensesp) {
|
||||||
|
UASSERT(sensesp, "Must be non-null");
|
||||||
|
|
||||||
auto it = m_activeMap.find(*sensesp);
|
auto it = m_activeMap.find(*sensesp);
|
||||||
// If found matching AstActive, return it
|
// If found matching AstActive, return it
|
||||||
|
|
@ -278,6 +281,11 @@ public:
|
||||||
void main(AstScope* nodep) { iterate(nodep); }
|
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
|
// Latch checking visitor
|
||||||
|
|
||||||
|
|
@ -312,10 +320,10 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
ActiveLatchCheckVisitor(AstNode* nodep, VAlwaysKwd kwd) {
|
ActiveLatchCheckVisitor(AstNode* nodep, bool expectLatch) {
|
||||||
m_graph.begin();
|
m_graph.begin();
|
||||||
iterate(nodep);
|
iterate(nodep);
|
||||||
m_graph.latchCheck(nodep, kwd == VAlwaysKwd::ALWAYS_LATCH);
|
m_graph.latchCheck(nodep, expectLatch);
|
||||||
}
|
}
|
||||||
virtual ~ActiveLatchCheckVisitor() = default;
|
virtual ~ActiveLatchCheckVisitor() = default;
|
||||||
};
|
};
|
||||||
|
|
@ -397,87 +405,24 @@ class ActiveVisitor final : public ActiveBaseVisitor {
|
||||||
private:
|
private:
|
||||||
// NODE STATE
|
// NODE STATE
|
||||||
// Each call to V3Const::constify
|
// 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
|
// AstNode::user4() Used by V3Const::constify, called below
|
||||||
|
|
||||||
// STATE
|
// STATE
|
||||||
ActiveNamer m_namer; // Tracking of active names
|
ActiveNamer m_namer; // Tracking of active names
|
||||||
AstCFunc* m_scopeFinalp = nullptr; // Final function for this scope
|
bool m_clockedProcess = false; // Whether current process is a clocked process
|
||||||
bool m_itemCombo = false; // Found a SenItem combo
|
bool m_allChanged = false; // Whether all SenItem in the SenTree are ET_CHANGED
|
||||||
bool m_itemSequent = false; // Found a SenItem sequential
|
bool m_walkingBody = false; // Walking body of a process
|
||||||
|
bool m_canBeComb = false; // Whether current clocked process can be turned into a comb process
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// METHODS
|
// 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) {
|
void visitAlways(AstNode* nodep, AstSenTree* oldsensesp, VAlwaysKwd kwd) {
|
||||||
// Move always to appropriate ACTIVE based on its sense list
|
// Move always to appropriate ACTIVE based on its sense list
|
||||||
if (oldsensesp && oldsensesp->sensesp() && oldsensesp->sensesp()->isNever()) {
|
if (oldsensesp && oldsensesp->sensesp() && oldsensesp->sensesp()->isNever()) {
|
||||||
|
|
@ -488,112 +433,152 @@ private:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read sensitivities
|
{
|
||||||
m_itemCombo = false;
|
const VNUser1InUse user1InUse;
|
||||||
m_itemSequent = false;
|
|
||||||
iterateAndNextNull(oldsensesp);
|
|
||||||
bool combo = m_itemCombo;
|
|
||||||
bool sequent = m_itemSequent;
|
|
||||||
|
|
||||||
if (!combo && !sequent) combo = true; // If no list, Verilog 2000: always @ (*)
|
// Walk sensitivity list
|
||||||
if (combo && sequent) {
|
m_clockedProcess = false;
|
||||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Mixed edge (pos/negedge) and activity "
|
m_allChanged = true;
|
||||||
"(no edge) sensitive activity list");
|
if (oldsensesp) {
|
||||||
sequent = false;
|
oldsensesp->unlinkFrBack();
|
||||||
}
|
iterateChildrenConst(oldsensesp);
|
||||||
|
}
|
||||||
AstActive* wantactivep = nullptr;
|
|
||||||
if (combo && !sequent) {
|
// If all SenItems are ET_CHANGE, then walk the body to determine if this process
|
||||||
// Combo: Relink to ACTIVE(combo)
|
// could be turned into a combinational process instead.
|
||||||
wantactivep = m_namer.getCActive(nodep->fileline());
|
if (m_allChanged) {
|
||||||
} else {
|
const VNUser2InUse user2InUse;
|
||||||
// Sequential: Build a ACTIVE(name)
|
m_walkingBody = true;
|
||||||
// OPTIMIZE: We could substitute a constant for things in the sense list, for example
|
m_canBeComb = true;
|
||||||
// always (posedge RESET) { if (RESET).... } we know RESET is true.
|
iterateChildrenConst(nodep);
|
||||||
// Summarize a long list of combo inputs as just "combo"
|
m_walkingBody = false;
|
||||||
#ifndef __COVERITY__ // Else dead code on next line.
|
if (m_canBeComb) m_clockedProcess = false;
|
||||||
if (combo) {
|
|
||||||
oldsensesp->addSensesp(new AstSenItem(nodep->fileline(), AstSenItem::Combo()));
|
|
||||||
}
|
}
|
||||||
#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
|
// Delete sensitivity list
|
||||||
if (oldsensesp) {
|
if (oldsensesp) VL_DO_DANGLING(oldsensesp->deleteTree(), oldsensesp);
|
||||||
VL_DO_DANGLING(oldsensesp->unlinkFrBackWithNext()->deleteTree(), oldsensesp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move node to new active
|
// Move node to new active
|
||||||
nodep->unlinkFrBack();
|
nodep->unlinkFrBack();
|
||||||
wantactivep->addStmtsp(nodep);
|
wantactivep->addStmtsp(nodep);
|
||||||
|
|
||||||
// Warn and/or convert any delayed assignments
|
// Warn and convert any delayed assignments
|
||||||
if (combo && !sequent) {
|
ActiveDlyVisitor{nodep,
|
||||||
ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB};
|
m_clockedProcess ? ActiveDlyVisitor::CT_SEQ : ActiveDlyVisitor::CT_COMB};
|
||||||
const ActiveLatchCheckVisitor latchvisitor{nodep, kwd};
|
|
||||||
} else if (!combo && sequent) {
|
// check combinational processes for latches
|
||||||
ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_SEQ};
|
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()) {
|
// VISITORS
|
||||||
// Empty always. Kill it.
|
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);
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
visitAlways(nodep, nodep->sensesp(), nodep->keyword());
|
visitAlways(nodep, nodep->sensesp(), nodep->keyword());
|
||||||
}
|
}
|
||||||
virtual void visit(AstAlwaysPostponed* nodep) override {
|
virtual void visit(AstAlwaysPostponed* nodep) override {
|
||||||
UINFO(4, " ALW " << nodep << endl);
|
if (!nodep->bodysp()) { // Empty always. Remove it now.
|
||||||
if (!nodep->bodysp()) {
|
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS);
|
visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS);
|
||||||
}
|
}
|
||||||
virtual void visit(AstAlwaysPublic* nodep) override {
|
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);
|
visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void visit(AstSenItem* nodep) override {
|
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 (nodep->varrefp()) {
|
||||||
if (const AstBasicDType* const basicp = nodep->varrefp()->dtypep()->basicp()) {
|
if (const AstBasicDType* const basicp = nodep->varrefp()->dtypep()->basicp()) {
|
||||||
if (basicp->isEventValue()) {
|
if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT);
|
||||||
// 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 (nodep->edgeType() == VEdgeType::ET_ANYEDGE) {
|
|
||||||
m_itemCombo = true;
|
nodep->sensp()->foreach<AstVarRef>([](const AstVarRef* refp) {
|
||||||
// Delete the sensitivity
|
refp->varp()->usedClock(true);
|
||||||
// We'll add it as a generic COMBO SenItem in a moment.
|
refp->varScopep()->user1(true);
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
});
|
||||||
} else if (nodep->varrefp()) {
|
}
|
||||||
// V3LinkResolve should have cleaned most of these up
|
|
||||||
if (!nodep->varrefp()->width1()) {
|
virtual void visit(AstVarRef* nodep) override {
|
||||||
nodep->v3warn(E_UNSUPPORTED,
|
AstVarScope* const vscp = nodep->varScopep();
|
||||||
"Unsupported: Non-single bit wide signal pos/negedge sensitivity: "
|
if (nodep->access().isWriteOnly()) {
|
||||||
<< nodep->varrefp()->prettyNameQ());
|
vscp->user2(true);
|
||||||
}
|
} else {
|
||||||
m_itemSequent = true;
|
// If the variable is read before it is written, and is not in the sensitivity list,
|
||||||
nodep->varrefp()->varp()->usedClock(true);
|
// 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(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:
|
public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
|
|
|
||||||
|
|
@ -36,20 +36,28 @@
|
||||||
// Active class functions
|
// Active class functions
|
||||||
|
|
||||||
class ActiveTopVisitor final : public VNVisitor {
|
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
|
// STATE
|
||||||
SenTreeFinder m_finder; // Find global sentree's / add them under the AstTopScope
|
SenTreeFinder m_finder; // Find global sentree's / add them under the AstTopScope
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
VL_DEBUG_FUNC; // Declare debug()
|
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
|
// VISITORS
|
||||||
virtual void visit(AstNodeModule* nodep) override {
|
virtual void visit(AstNodeModule* nodep) override {
|
||||||
// Create required actives and add to module
|
// Create required actives and add to module
|
||||||
|
|
@ -70,15 +78,6 @@ private:
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||||
return;
|
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.
|
// Move the SENTREE for each active up to the global level.
|
||||||
// This way we'll easily see what clock domains are identical
|
// This way we'll easily see what clock domains are identical
|
||||||
AstSenTree* const wantp = m_finder.getSenTree(sensesp);
|
AstSenTree* const wantp = m_finder.getSenTree(sensesp);
|
||||||
|
|
@ -97,8 +96,23 @@ private:
|
||||||
}
|
}
|
||||||
nodep->sensesp(wantp);
|
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
|
virtual void visit(AstNodeProcedure* nodep) override { // LCOV_EXCL_LINE
|
||||||
nodep->v3fatalSrc("Node should have been under ACTIVE");
|
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
|
// in V3Const::visit AstSenTree
|
||||||
ET_ILLEGAL,
|
ET_ILLEGAL,
|
||||||
// Involving a variable
|
// Involving a variable
|
||||||
ET_ANYEDGE, // Default for sensitivities; rip them out
|
ET_CHANGED, // Value changed
|
||||||
ET_BOTHEDGE, // POSEDGE | NEGEDGE
|
ET_BOTHEDGE, // POSEDGE | NEGEDGE (i.e.: 'edge' in Verilog)
|
||||||
ET_POSEDGE,
|
ET_POSEDGE,
|
||||||
ET_NEGEDGE,
|
ET_NEGEDGE,
|
||||||
ET_HIGHEDGE, // Is high now (latches)
|
ET_EVENT, // VlEvent::isFired
|
||||||
ET_LOWEDGE, // Is low now (latches)
|
ET_DPIEXPORT, // Used exclusively to check the AstNetlist::dpiExportTriggerp()
|
||||||
// Not involving anything
|
// Involving an expression
|
||||||
|
ET_TRUE,
|
||||||
|
//
|
||||||
ET_COMBO, // Sensitive to all combo inputs to this block
|
ET_COMBO, // Sensitive to all combo inputs to this block
|
||||||
ET_INITIAL, // User initial statements
|
ET_HYBRID, // This is like ET_COMB, but with explicit sensitivity to an expression
|
||||||
ET_SETTLE, // Like combo but for initial wire resolutions after initial statement
|
ET_STATIC, // static variable initializers (runs before 'initial')
|
||||||
|
ET_INITIAL, // 'initial' statements
|
||||||
|
ET_FINAL, // 'final' statements
|
||||||
ET_NEVER // Never occurs (optimized away)
|
ET_NEVER // Never occurs (optimized away)
|
||||||
};
|
};
|
||||||
enum en m_e;
|
enum en m_e;
|
||||||
bool clockedStmt() const {
|
bool clockedStmt() const {
|
||||||
static const bool clocked[]
|
static const bool clocked[] = {
|
||||||
= {false, false, true, true, true, true, true, false, false, false};
|
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];
|
return clocked[m_e];
|
||||||
}
|
}
|
||||||
VEdgeType invert() const {
|
VEdgeType invert() const {
|
||||||
switch (m_e) {
|
switch (m_e) {
|
||||||
case ET_ANYEDGE: return ET_ANYEDGE;
|
case ET_CHANGED: return ET_CHANGED;
|
||||||
case ET_BOTHEDGE: return ET_BOTHEDGE;
|
case ET_BOTHEDGE: return ET_BOTHEDGE;
|
||||||
case ET_POSEDGE: return ET_NEGEDGE;
|
case ET_POSEDGE: return ET_NEGEDGE;
|
||||||
case ET_NEGEDGE: return ET_POSEDGE;
|
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()");
|
default: UASSERT_STATIC(0, "Inverting bad edgeType()");
|
||||||
}
|
}
|
||||||
return VEdgeType::ET_ILLEGAL;
|
return VEdgeType::ET_ILLEGAL;
|
||||||
}
|
}
|
||||||
const char* ascii() const {
|
const char* ascii() const {
|
||||||
static const char* const names[]
|
static const char* const names[] = {"%E-edge",
|
||||||
= {"%E-edge", "ANY", "BOTH", "POS", "NEG", "HIGH",
|
"CHANGED",
|
||||||
"LOW", "COMBO", "INITIAL", "SETTLE", "NEVER"};
|
"BOTH",
|
||||||
|
"POS",
|
||||||
|
"NEG",
|
||||||
|
"EVENT",
|
||||||
|
"DPIEXPORT"
|
||||||
|
"TRUE",
|
||||||
|
"COMBO",
|
||||||
|
"HYBRID",
|
||||||
|
"STATIC",
|
||||||
|
"INITIAL",
|
||||||
|
"FINAL",
|
||||||
|
"NEVER"};
|
||||||
return names[m_e];
|
return names[m_e];
|
||||||
}
|
}
|
||||||
const char* verilogKwd() const {
|
const char* verilogKwd() const {
|
||||||
static const char* const names[]
|
static const char* const names[] = {
|
||||||
= {"%E-edge", "[any]", "edge", "posedge", "negedge", "[high]",
|
"%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[dpiexport]",
|
||||||
"[low]", "*", "[initial]", "[settle]", "[never]"};
|
"[true]", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
|
||||||
return names[m_e];
|
return names[m_e];
|
||||||
}
|
}
|
||||||
// Return true iff this and the other have mutually exclusive transitions
|
// Return true iff this and the other have mutually exclusive transitions
|
||||||
bool exclusiveEdge(const VEdgeType& other) const {
|
bool exclusiveEdge(const VEdgeType& other) const {
|
||||||
switch (m_e) {
|
switch (m_e) {
|
||||||
case VEdgeType::ET_POSEDGE:
|
case VEdgeType::ET_POSEDGE:
|
||||||
switch (other.m_e) {
|
if (other.m_e == VEdgeType::ET_NEGEDGE) return true;
|
||||||
case VEdgeType::ET_NEGEDGE: // FALLTHRU
|
|
||||||
case VEdgeType::ET_LOWEDGE: return true;
|
|
||||||
default:;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case VEdgeType::ET_NEGEDGE:
|
case VEdgeType::ET_NEGEDGE:
|
||||||
switch (other.m_e) {
|
if (other.m_e == VEdgeType::ET_POSEDGE) return true;
|
||||||
case VEdgeType::ET_POSEDGE: // FALLTHRU
|
|
||||||
case VEdgeType::ET_HIGHEDGE: return true;
|
|
||||||
default:;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:;
|
default: break;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -371,7 +392,7 @@ public:
|
||||||
TYPENAME, // V3Width processes
|
TYPENAME, // V3Width processes
|
||||||
//
|
//
|
||||||
VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes
|
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_FORCEABLE, // V3LinkParse moves to AstVar::isForceable
|
||||||
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
|
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
|
||||||
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
|
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
|
||||||
|
|
@ -427,7 +448,7 @@ public:
|
||||||
BIT,
|
BIT,
|
||||||
BYTE,
|
BYTE,
|
||||||
CHANDLE,
|
CHANDLE,
|
||||||
EVENTVALUE, // See comments in t_event_copy as to why this is EVENTVALUE
|
EVENT,
|
||||||
INT,
|
INT,
|
||||||
INTEGER,
|
INTEGER,
|
||||||
LOGIC,
|
LOGIC,
|
||||||
|
|
@ -441,6 +462,7 @@ public:
|
||||||
SCOPEPTR,
|
SCOPEPTR,
|
||||||
CHARPTR,
|
CHARPTR,
|
||||||
MTASKSTATE,
|
MTASKSTATE,
|
||||||
|
TRIGGERVEC,
|
||||||
// Unsigned and two state; fundamental types
|
// Unsigned and two state; fundamental types
|
||||||
UINT32,
|
UINT32,
|
||||||
UINT64,
|
UINT64,
|
||||||
|
|
@ -452,18 +474,20 @@ public:
|
||||||
enum en m_e;
|
enum en m_e;
|
||||||
const char* ascii() const {
|
const char* ascii() const {
|
||||||
static const char* const names[]
|
static const char* const names[]
|
||||||
= {"%E-unk", "bit", "byte", "chandle", "event",
|
= {"%E-unk", "bit", "byte", "chandle", "event",
|
||||||
"int", "integer", "logic", "longint", "real",
|
"int", "integer", "logic", "longint", "real",
|
||||||
"shortint", "time", "string", "VerilatedScope*", "char*",
|
"shortint", "time", "string", "VerilatedScope*", "char*",
|
||||||
"VlMTaskState", "IData", "QData", "LOGIC_IMPLICIT", " MAX"};
|
"VlMTaskState", "VlTriggerVec", "IData", "QData", "LOGIC_IMPLICIT",
|
||||||
|
" MAX"};
|
||||||
return names[m_e];
|
return names[m_e];
|
||||||
}
|
}
|
||||||
const char* dpiType() const {
|
const char* dpiType() const {
|
||||||
static const char* const names[]
|
static const char* const names[]
|
||||||
= {"%E-unk", "svBit", "char", "void*", "char",
|
= {"%E-unk", "svBit", "char", "void*", "char",
|
||||||
"int", "%E-integer", "svLogic", "long long", "double",
|
"int", "%E-integer", "svLogic", "long long", "double",
|
||||||
"short", "%E-time", "const char*", "dpiScope", "const char*",
|
"short", "%E-time", "const char*", "dpiScope", "const char*",
|
||||||
"%E-mtaskstate", "IData", "QData", "%E-logic-implct", " MAX"};
|
"%E-mtaskstate", "%E-triggervec", "IData", "QData", "%E-logic-implct",
|
||||||
|
" MAX"};
|
||||||
return names[m_e];
|
return names[m_e];
|
||||||
}
|
}
|
||||||
static void selfTest() {
|
static void selfTest() {
|
||||||
|
|
@ -484,7 +508,7 @@ public:
|
||||||
case BIT: return 1; // scalar, can't bit extract unless ranged
|
case BIT: return 1; // scalar, can't bit extract unless ranged
|
||||||
case BYTE: return 8;
|
case BYTE: return 8;
|
||||||
case CHANDLE: return 64;
|
case CHANDLE: return 64;
|
||||||
case EVENTVALUE: return 1;
|
case EVENT: return 1;
|
||||||
case INT: return 32;
|
case INT: return 32;
|
||||||
case INTEGER: return 32;
|
case INTEGER: return 32;
|
||||||
case LOGIC: return 1; // scalar, can't bit extract unless ranged
|
case LOGIC: return 1; // scalar, can't bit extract unless ranged
|
||||||
|
|
@ -496,6 +520,7 @@ public:
|
||||||
case SCOPEPTR: return 0; // opaque
|
case SCOPEPTR: return 0; // opaque
|
||||||
case CHARPTR: return 0; // opaque
|
case CHARPTR: return 0; // opaque
|
||||||
case MTASKSTATE: return 0; // opaque
|
case MTASKSTATE: return 0; // opaque
|
||||||
|
case TRIGGERVEC: return 0; // opaque
|
||||||
case UINT32: return 32;
|
case UINT32: return 32;
|
||||||
case UINT64: return 64;
|
case UINT64: return 64;
|
||||||
default: return 0;
|
default: return 0;
|
||||||
|
|
@ -506,15 +531,14 @@ public:
|
||||||
|| m_e == DOUBLE;
|
|| m_e == DOUBLE;
|
||||||
}
|
}
|
||||||
bool isUnsigned() const {
|
bool isUnsigned() const {
|
||||||
return m_e == CHANDLE || m_e == EVENTVALUE || m_e == STRING || m_e == SCOPEPTR
|
return m_e == CHANDLE || m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|
||||||
|| m_e == CHARPTR || m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC
|
|| m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC || m_e == TIME;
|
||||||
|| m_e == TIME;
|
|
||||||
}
|
}
|
||||||
bool isFourstate() const {
|
bool isFourstate() const {
|
||||||
return m_e == INTEGER || m_e == LOGIC || m_e == LOGIC_IMPLICIT || m_e == TIME;
|
return m_e == INTEGER || m_e == LOGIC || m_e == LOGIC_IMPLICIT || m_e == TIME;
|
||||||
}
|
}
|
||||||
bool isZeroInit() const { // Otherwise initializes to X
|
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);
|
|| m_e == LONGINT || m_e == SHORTINT || m_e == STRING || m_e == DOUBLE);
|
||||||
}
|
}
|
||||||
bool isIntNumeric() const { // Enum increment supported
|
bool isIntNumeric() const { // Enum increment supported
|
||||||
|
|
@ -532,11 +556,11 @@ public:
|
||||||
|| m_e == DOUBLE || m_e == SHORTINT || m_e == UINT32 || m_e == UINT64);
|
|| m_e == DOUBLE || m_e == SHORTINT || m_e == UINT32 || m_e == UINT64);
|
||||||
}
|
}
|
||||||
bool isOpaque() const { // IE not a simple number we can bit optimize
|
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
|
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|
||||||
|| m_e == DOUBLE);
|
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DOUBLE);
|
||||||
}
|
}
|
||||||
bool isDouble() const { return 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 isString() const { return m_e == STRING; }
|
||||||
bool isMTaskState() const { return m_e == MTASKSTATE; }
|
bool isMTaskState() const { return m_e == MTASKSTATE; }
|
||||||
// Does this represent a C++ LiteralType? (can be constexpr)
|
// 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
|
if (VN_IS(nodep, NodeStmt)) return false; // Should be directly under CFunc
|
||||||
return true;
|
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) {
|
inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) {
|
||||||
if (!rhs) {
|
if (!rhs) {
|
||||||
|
|
@ -2137,7 +2170,7 @@ public:
|
||||||
VNRef(U&& x)
|
VNRef(U&& x)
|
||||||
: std::reference_wrapper<T_Node>{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} {}
|
: std::reference_wrapper<T_Node>{other} {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2655,6 +2688,7 @@ public:
|
||||||
return text() == asamep->text();
|
return text() == asamep->text();
|
||||||
}
|
}
|
||||||
const string& text() const { return m_text; }
|
const string& text() const { return m_text; }
|
||||||
|
void text(const string& value) { m_text = value; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class AstNodeDType VL_NOT_FINAL : public AstNode {
|
class AstNodeDType VL_NOT_FINAL : public AstNode {
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,10 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
|
||||||
info.m_type = "std::string";
|
info.m_type = "std::string";
|
||||||
} else if (bdtypep->keyword().isMTaskState()) {
|
} else if (bdtypep->keyword().isMTaskState()) {
|
||||||
info.m_type = "VlMTaskVertex";
|
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
|
} else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width
|
||||||
info.m_type = "CData" + bitvec;
|
info.m_type = "CData" + bitvec;
|
||||||
} else if (dtypep->widthMin() <= 16) {
|
} else if (dtypep->widthMin() <= 16) {
|
||||||
|
|
@ -854,6 +858,29 @@ string AstScope::nameDotless() const {
|
||||||
return out;
|
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 AstScopeName::scopePrettyNameFormatter(AstText* scopeTextp) const {
|
||||||
string out;
|
string out;
|
||||||
for (AstText* textp = scopeTextp; textp; textp = VN_AS(textp->nextp(), Text)) {
|
for (AstText* textp = scopeTextp; textp; textp = VN_AS(textp->nextp(), Text)) {
|
||||||
|
|
@ -886,10 +913,10 @@ bool AstSenTree::hasClocked() const {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool AstSenTree::hasSettle() const {
|
bool AstSenTree::hasStatic() const {
|
||||||
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
|
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
|
||||||
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
|
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
|
||||||
if (senp->isSettle()) return true;
|
if (senp->isStatic()) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -900,6 +927,13 @@ bool AstSenTree::hasInitial() const {
|
||||||
}
|
}
|
||||||
return false;
|
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 {
|
bool AstSenTree::hasCombo() const {
|
||||||
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
|
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
|
||||||
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
|
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
|
||||||
|
|
@ -907,6 +941,13 @@ bool AstSenTree::hasCombo() const {
|
||||||
}
|
}
|
||||||
return false;
|
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)
|
AstTypeTable::AstTypeTable(FileLine* fl)
|
||||||
: ASTGEN_SUPER_TypeTable(fl) {
|
: ASTGEN_SUPER_TypeTable(fl) {
|
||||||
|
|
@ -1738,7 +1779,6 @@ void AstVar::dump(std::ostream& str) const {
|
||||||
if (isSigPublic()) str << " [P]";
|
if (isSigPublic()) str << " [P]";
|
||||||
if (isLatched()) str << " [LATCHED]";
|
if (isLatched()) str << " [LATCHED]";
|
||||||
if (isUsedLoopIdx()) str << " [LOOP]";
|
if (isUsedLoopIdx()) str << " [LOOP]";
|
||||||
if (attrClockEn()) str << " [aCLKEN]";
|
|
||||||
if (attrIsolateAssign()) str << " [aISO]";
|
if (attrIsolateAssign()) str << " [aISO]";
|
||||||
if (attrFileDescr()) str << " [aFD]";
|
if (attrFileDescr()) str << " [aFD]";
|
||||||
if (isFuncReturn()) {
|
if (isFuncReturn()) {
|
||||||
|
|
|
||||||
120
src/V3AstNodes.h
120
src/V3AstNodes.h
|
|
@ -934,7 +934,8 @@ public:
|
||||||
}
|
}
|
||||||
bool isBitLogic() const { return keyword().isBitLogic(); }
|
bool isBitLogic() const { return keyword().isBitLogic(); }
|
||||||
bool isDouble() const { return keyword().isDouble(); }
|
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 isOpaque() const { return keyword().isOpaque(); }
|
||||||
bool isString() const { return keyword().isString(); }
|
bool isString() const { return keyword().isString(); }
|
||||||
bool isZeroInit() const { return keyword().isZeroInit(); }
|
bool isZeroInit() const { return keyword().isZeroInit(); }
|
||||||
|
|
@ -1979,7 +1980,6 @@ private:
|
||||||
bool m_usedLoopIdx : 1; // Variable subject of for unrolling
|
bool m_usedLoopIdx : 1; // Variable subject of for unrolling
|
||||||
bool m_funcLocal : 1; // Local variable for a function
|
bool m_funcLocal : 1; // Local variable for a function
|
||||||
bool m_funcReturn : 1; // Return 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_attrScBv : 1; // User force bit vector attribute
|
||||||
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
|
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
|
||||||
bool m_attrSFormat : 1; // User sformat attribute
|
bool m_attrSFormat : 1; // User sformat attribute
|
||||||
|
|
@ -2019,7 +2019,6 @@ private:
|
||||||
m_sigUserRWPublic = false;
|
m_sigUserRWPublic = false;
|
||||||
m_funcLocal = false;
|
m_funcLocal = false;
|
||||||
m_funcReturn = false;
|
m_funcReturn = false;
|
||||||
m_attrClockEn = false;
|
|
||||||
m_attrScBv = false;
|
m_attrScBv = false;
|
||||||
m_attrIsolateAssign = false;
|
m_attrIsolateAssign = false;
|
||||||
m_attrSFormat = false;
|
m_attrSFormat = false;
|
||||||
|
|
@ -2157,7 +2156,6 @@ public:
|
||||||
virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); }
|
virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); }
|
||||||
void ansi(bool flag) { m_ansi = flag; }
|
void ansi(bool flag) { m_ansi = flag; }
|
||||||
void declTyped(bool flag) { m_declTyped = flag; }
|
void declTyped(bool flag) { m_declTyped = flag; }
|
||||||
void attrClockEn(bool flag) { m_attrClockEn = flag; }
|
|
||||||
void attrClocker(VVarAttrClocker flag) { m_attrClocker = flag; }
|
void attrClocker(VVarAttrClocker flag) { m_attrClocker = flag; }
|
||||||
void attrFileDescr(bool flag) { m_fileDescr = flag; }
|
void attrFileDescr(bool flag) { m_fileDescr = flag; }
|
||||||
void attrScClocked(bool flag) { m_scClocked = flag; }
|
void attrScClocked(bool flag) { m_scClocked = flag; }
|
||||||
|
|
@ -2262,7 +2260,6 @@ public:
|
||||||
bool isFuncReturn() const { return m_funcReturn; }
|
bool isFuncReturn() const { return m_funcReturn; }
|
||||||
bool isPullup() const { return m_isPullup; }
|
bool isPullup() const { return m_isPullup; }
|
||||||
bool isPulldown() const { return m_isPulldown; }
|
bool isPulldown() const { return m_isPulldown; }
|
||||||
bool attrClockEn() const { return m_attrClockEn; }
|
|
||||||
bool attrScBv() const { return m_attrScBv; }
|
bool attrScBv() const { return m_attrScBv; }
|
||||||
bool attrFileDescr() const { return m_fileDescr; }
|
bool attrFileDescr() const { return m_fileDescr; }
|
||||||
bool attrScClocked() const { return m_scClocked; }
|
bool attrScClocked() const { return m_scClocked; }
|
||||||
|
|
@ -2276,14 +2273,13 @@ public:
|
||||||
void propagateAttrFrom(AstVar* fromp) {
|
void propagateAttrFrom(AstVar* fromp) {
|
||||||
// This is getting connected to fromp; keep attributes
|
// This is getting connected to fromp; keep attributes
|
||||||
// Note the method below too
|
// Note the method below too
|
||||||
if (fromp->attrClockEn()) attrClockEn(true);
|
|
||||||
if (fromp->attrFileDescr()) attrFileDescr(true);
|
if (fromp->attrFileDescr()) attrFileDescr(true);
|
||||||
if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
|
if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
|
||||||
if (fromp->isContinuously()) isContinuously(true);
|
if (fromp->isContinuously()) isContinuously(true);
|
||||||
}
|
}
|
||||||
bool gateMultiInputOptimizable() const {
|
bool gateMultiInputOptimizable() const {
|
||||||
// Ok to gate optimize; must return false if propagateAttrFrom would do anything
|
// Ok to gate optimize; must return false if propagateAttrFrom would do anything
|
||||||
return (!attrClockEn() && !isUsedClock());
|
return !isUsedClock();
|
||||||
}
|
}
|
||||||
void combineType(AstVar* typevarp) {
|
void combineType(AstVar* typevarp) {
|
||||||
// This is same as typevarp (for combining input & reg decls)
|
// This is same as typevarp (for combining input & reg decls)
|
||||||
|
|
@ -2372,15 +2368,20 @@ public:
|
||||||
string nameDotless() const;
|
string nameDotless() const;
|
||||||
string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); }
|
string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); }
|
||||||
AstNodeModule* modp() const { return m_modp; }
|
AstNodeModule* modp() const { return m_modp; }
|
||||||
|
// op1: AstVarScope's
|
||||||
|
AstVarScope* varsp() const { return VN_AS(op1p(), VarScope); }
|
||||||
void addVarp(AstVarScope* nodep) { addOp1p((AstNode*)nodep); }
|
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); }
|
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; }
|
AstScope* aboveScopep() const { return m_aboveScopep; }
|
||||||
AstCell* aboveCellp() const { return m_aboveCellp; }
|
AstCell* aboveCellp() const { return m_aboveCellp; }
|
||||||
bool isTop() const { return aboveScopep() == nullptr; } // At top of hierarchy
|
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 {
|
class AstTopScope final : public AstNode {
|
||||||
|
|
@ -3317,15 +3318,16 @@ class AstSenItem final : public AstNode {
|
||||||
private:
|
private:
|
||||||
VEdgeType m_edgeType; // Edge type
|
VEdgeType m_edgeType; // Edge type
|
||||||
public:
|
public:
|
||||||
class Combo {}; // for creator type-overload selection
|
class Combo {}; // for constructor type-overload selection
|
||||||
class Illegal {}; // for creator type-overload selection
|
class Illegal {}; // for constructor type-overload selection
|
||||||
class Initial {}; // for creator type-overload selection
|
class Static {}; // for constructor type-overload selection
|
||||||
class Settle {}; // for creator type-overload selection
|
class Initial {}; // for constructor type-overload selection
|
||||||
class Never {}; // for creator type-overload selection
|
class Final {}; // for constructor type-overload selection
|
||||||
AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* varrefp)
|
class Never {}; // for constructor type-overload selection
|
||||||
|
AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* senp)
|
||||||
: ASTGEN_SUPER_SenItem(fl)
|
: ASTGEN_SUPER_SenItem(fl)
|
||||||
, m_edgeType{edgeType} {
|
, m_edgeType{edgeType} {
|
||||||
setOp1p(varrefp);
|
setOp1p(senp);
|
||||||
}
|
}
|
||||||
AstSenItem(FileLine* fl, Combo)
|
AstSenItem(FileLine* fl, Combo)
|
||||||
: ASTGEN_SUPER_SenItem(fl)
|
: ASTGEN_SUPER_SenItem(fl)
|
||||||
|
|
@ -3333,12 +3335,15 @@ public:
|
||||||
AstSenItem(FileLine* fl, Illegal)
|
AstSenItem(FileLine* fl, Illegal)
|
||||||
: ASTGEN_SUPER_SenItem(fl)
|
: ASTGEN_SUPER_SenItem(fl)
|
||||||
, m_edgeType{VEdgeType::ET_ILLEGAL} {}
|
, m_edgeType{VEdgeType::ET_ILLEGAL} {}
|
||||||
|
AstSenItem(FileLine* fl, Static)
|
||||||
|
: ASTGEN_SUPER_SenItem(fl)
|
||||||
|
, m_edgeType{VEdgeType::ET_STATIC} {}
|
||||||
AstSenItem(FileLine* fl, Initial)
|
AstSenItem(FileLine* fl, Initial)
|
||||||
: ASTGEN_SUPER_SenItem(fl)
|
: ASTGEN_SUPER_SenItem(fl)
|
||||||
, m_edgeType{VEdgeType::ET_INITIAL} {}
|
, m_edgeType{VEdgeType::ET_INITIAL} {}
|
||||||
AstSenItem(FileLine* fl, Settle)
|
AstSenItem(FileLine* fl, Final)
|
||||||
: ASTGEN_SUPER_SenItem(fl)
|
: ASTGEN_SUPER_SenItem(fl)
|
||||||
, m_edgeType{VEdgeType::ET_SETTLE} {}
|
, m_edgeType{VEdgeType::ET_FINAL} {}
|
||||||
AstSenItem(FileLine* fl, Never)
|
AstSenItem(FileLine* fl, Never)
|
||||||
: ASTGEN_SUPER_SenItem(fl)
|
: ASTGEN_SUPER_SenItem(fl)
|
||||||
, m_edgeType{VEdgeType::ET_NEVER} {}
|
, m_edgeType{VEdgeType::ET_NEVER} {}
|
||||||
|
|
@ -3347,23 +3352,23 @@ public:
|
||||||
virtual bool same(const AstNode* samep) const override {
|
virtual bool same(const AstNode* samep) const override {
|
||||||
return edgeType() == static_cast<const AstSenItem*>(samep)->edgeType();
|
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) {
|
void edgeType(VEdgeType type) {
|
||||||
m_edgeType = type;
|
m_edgeType = type;
|
||||||
editCountInc();
|
editCountInc();
|
||||||
} // * = Posedge/negedge
|
}
|
||||||
AstNode* sensp() const { return op1p(); } // op1 = Signal sensitized
|
// op1 = Expression sensitized, if any
|
||||||
AstNodeVarRef* varrefp() const {
|
AstNode* sensp() const { return op1p(); }
|
||||||
return VN_CAST(op1p(), NodeVarRef);
|
AstNodeVarRef* varrefp() const { return VN_CAST(op1p(), NodeVarRef); }
|
||||||
} // op1 = Signal sensitized
|
|
||||||
//
|
//
|
||||||
bool isClocked() const { return edgeType().clockedStmt(); }
|
bool isClocked() const { return edgeType().clockedStmt(); }
|
||||||
bool isCombo() const { return edgeType() == VEdgeType::ET_COMBO; }
|
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 isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; }
|
||||||
|
bool isFinal() const { return edgeType() == VEdgeType::ET_FINAL; }
|
||||||
bool isIllegal() const { return edgeType() == VEdgeType::ET_ILLEGAL; }
|
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 isNever() const { return edgeType() == VEdgeType::ET_NEVER; }
|
||||||
bool hasVar() const { return !(isCombo() || isInitial() || isSettle() || isNever()); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AstSenTree final : public AstNode {
|
class AstSenTree final : public AstNode {
|
||||||
|
|
@ -3387,9 +3392,11 @@ public:
|
||||||
void multi(bool flag) { m_multi = true; }
|
void multi(bool flag) { m_multi = true; }
|
||||||
// METHODS
|
// METHODS
|
||||||
bool hasClocked() const; // Includes a clocked statement
|
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 hasInitial() const; // Includes a INITIAL SenItem
|
||||||
|
bool hasFinal() const; // Includes a FINAL SenItem
|
||||||
bool hasCombo() const; // Includes a COMBO SenItem
|
bool hasCombo() const; // Includes a COMBO SenItem
|
||||||
|
bool hasHybrid() const; // Includes a HYBRID SenItem
|
||||||
};
|
};
|
||||||
|
|
||||||
class AstFinal final : public AstNodeProcedure {
|
class AstFinal final : public AstNodeProcedure {
|
||||||
|
|
@ -3594,6 +3601,20 @@ public:
|
||||||
AstNode* lhsp() const { return op1p(); }
|
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 {
|
class AstAssignPre final : public AstNodeAssign {
|
||||||
// Like Assign, but predelayed assignment requiring special order handling
|
// Like Assign, but predelayed assignment requiring special order handling
|
||||||
public:
|
public:
|
||||||
|
|
@ -4827,24 +4848,6 @@ public:
|
||||||
AstJumpLabel* labelp() const { return m_labelp; }
|
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 {
|
class AstConsAssoc final : public AstNodeMath {
|
||||||
// Construct an assoc array and return object, '{}
|
// Construct an assoc array and return object, '{}
|
||||||
// Parents: math
|
// Parents: math
|
||||||
|
|
@ -5429,10 +5432,7 @@ public:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
virtual void cloneRelink() override {
|
virtual void cloneRelink() override {
|
||||||
if (m_sensesp->clonep()) {
|
if (m_sensesp->clonep()) m_sensesp = m_sensesp->clonep();
|
||||||
m_sensesp = m_sensesp->clonep();
|
|
||||||
UASSERT(m_sensesp, "Bad clone cross link: " << this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Statements are broken into pieces, as some must come before others.
|
// Statements are broken into pieces, as some must come before others.
|
||||||
void sensesp(AstSenTree* nodep) { m_sensesp = nodep; }
|
void sensesp(AstSenTree* nodep) { m_sensesp = nodep; }
|
||||||
|
|
@ -5440,13 +5440,12 @@ public:
|
||||||
// op1 = Sensitivity tree, if a clocked block in early stages
|
// op1 = Sensitivity tree, if a clocked block in early stages
|
||||||
void sensesStorep(AstSenTree* nodep) { addOp1p(nodep); }
|
void sensesStorep(AstSenTree* nodep) { addOp1p(nodep); }
|
||||||
AstSenTree* sensesStorep() const { return VN_AS(op1p(), SenTree); }
|
AstSenTree* sensesStorep() const { return VN_AS(op1p(), SenTree); }
|
||||||
// op2 = Combo logic
|
// op2 = Logic
|
||||||
AstNode* stmtsp() const { return op2p(); }
|
AstNode* stmtsp() const { return op2p(); }
|
||||||
void addStmtsp(AstNode* nodep) { addOp2p(nodep); }
|
void addStmtsp(AstNode* nodep) { addOp2p(nodep); }
|
||||||
// METHODS
|
// METHODS
|
||||||
bool hasInitial() const { return m_sensesp->hasInitial(); }
|
|
||||||
bool hasSettle() const { return m_sensesp->hasSettle(); }
|
|
||||||
bool hasClocked() const { return m_sensesp->hasClocked(); }
|
bool hasClocked() const { return m_sensesp->hasClocked(); }
|
||||||
|
bool hasCombo() const { return m_sensesp->hasCombo(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class AstAttrOf final : public AstNode {
|
class AstAttrOf final : public AstNode {
|
||||||
|
|
@ -8884,7 +8883,6 @@ private:
|
||||||
bool m_isTrace : 1; // Function is related to tracing
|
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_dontCombine : 1; // V3Combine shouldn't compare this func tree, it's special
|
||||||
bool m_declPrivate : 1; // Declare it private
|
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_slow : 1; // Slow routine, called once or just at init time
|
||||||
bool m_funcPublic : 1; // From user public task/function
|
bool m_funcPublic : 1; // From user public task/function
|
||||||
bool m_isConstructor : 1; // Is C class constructor
|
bool m_isConstructor : 1; // Is C class constructor
|
||||||
|
|
@ -8913,7 +8911,6 @@ public:
|
||||||
m_isTrace = false;
|
m_isTrace = false;
|
||||||
m_dontCombine = false;
|
m_dontCombine = false;
|
||||||
m_declPrivate = false;
|
m_declPrivate = false;
|
||||||
m_isFinal = false;
|
|
||||||
m_slow = false;
|
m_slow = false;
|
||||||
m_funcPublic = false;
|
m_funcPublic = false;
|
||||||
m_isConstructor = false;
|
m_isConstructor = false;
|
||||||
|
|
@ -8971,8 +8968,6 @@ public:
|
||||||
bool dontInline() const { return dontCombine() || slow() || funcPublic(); }
|
bool dontInline() const { return dontCombine() || slow() || funcPublic(); }
|
||||||
bool declPrivate() const { return m_declPrivate; }
|
bool declPrivate() const { return m_declPrivate; }
|
||||||
void declPrivate(bool flag) { m_declPrivate = flag; }
|
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; }
|
bool slow() const { return m_slow; }
|
||||||
void slow(bool flag) { m_slow = flag; }
|
void slow(bool flag) { m_slow = flag; }
|
||||||
bool funcPublic() const { return m_funcPublic; }
|
bool funcPublic() const { return m_funcPublic; }
|
||||||
|
|
@ -9332,11 +9327,11 @@ private:
|
||||||
AstConstPool* const m_constPoolp; // Reference to constant pool, for faster lookup
|
AstConstPool* const m_constPoolp; // Reference to constant pool, for faster lookup
|
||||||
AstPackage* m_dollarUnitPkgp = nullptr; // $unit
|
AstPackage* m_dollarUnitPkgp = nullptr; // $unit
|
||||||
AstCFunc* m_evalp = nullptr; // The '_eval' function
|
AstCFunc* m_evalp = nullptr; // The '_eval' function
|
||||||
|
AstCFunc* m_evalNbap = nullptr; // The '_eval__nba' function
|
||||||
AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable
|
AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable
|
||||||
AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module
|
AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module
|
||||||
VTimescale m_timeunit; // Global time unit
|
VTimescale m_timeunit; // Global time unit
|
||||||
VTimescale m_timeprecision; // Global time precision
|
VTimescale m_timeprecision; // Global time precision
|
||||||
bool m_changeRequest = false; // Have _change_request method
|
|
||||||
bool m_timescaleSpecified = false; // Input HDL specified timescale
|
bool m_timescaleSpecified = false; // Input HDL specified timescale
|
||||||
uint32_t m_nextFreeMTaskID = 1; // Next unique MTask ID within netlist
|
uint32_t m_nextFreeMTaskID = 1; // Next unique MTask ID within netlist
|
||||||
// starts at 1 so 0 means no MTask ID
|
// starts at 1 so 0 means no MTask ID
|
||||||
|
|
@ -9367,8 +9362,6 @@ public:
|
||||||
void addFilesp(AstNodeFile* filep) { addOp2p(filep); }
|
void addFilesp(AstNodeFile* filep) { addOp2p(filep); }
|
||||||
void addMiscsp(AstNode* nodep) { addOp3p(nodep); }
|
void addMiscsp(AstNode* nodep) { addOp3p(nodep); }
|
||||||
AstTypeTable* typeTablep() { return m_typeTablep; }
|
AstTypeTable* typeTablep() { return m_typeTablep; }
|
||||||
void changeRequest(bool specified) { m_changeRequest = specified; }
|
|
||||||
bool changeRequest() const { return m_changeRequest; }
|
|
||||||
AstConstPool* constPoolp() { return m_constPoolp; }
|
AstConstPool* constPoolp() { return m_constPoolp; }
|
||||||
AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; }
|
AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; }
|
||||||
AstPackage* dollarUnitPkgAddp() {
|
AstPackage* dollarUnitPkgAddp() {
|
||||||
|
|
@ -9382,8 +9375,11 @@ public:
|
||||||
}
|
}
|
||||||
return m_dollarUnitPkgp;
|
return m_dollarUnitPkgp;
|
||||||
}
|
}
|
||||||
|
|
||||||
AstCFunc* evalp() const { return m_evalp; }
|
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; }
|
AstVarScope* dpiExportTriggerp() const { return m_dpiExportTriggerp; }
|
||||||
void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; }
|
void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; }
|
||||||
AstTopScope* topScopep() const { return m_topScopep; }
|
AstTopScope* topScopep() const { return m_topScopep; }
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,9 @@ void V3CCtors::cctorsAll() {
|
||||||
for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) {
|
for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) {
|
||||||
if (AstVar* const varp = VN_CAST(np, Var)) {
|
if (AstVar* const varp = VN_CAST(np, Var)) {
|
||||||
if (!varp->isIfaceParent() && !varp->isIfaceRef() && !varp->noReset()
|
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};
|
const auto vrefp = new AstVarRef{varp->fileline(), varp, VAccess::WRITE};
|
||||||
var_reset.add(new AstCReset{varp->fileline(), vrefp});
|
var_reset.add(new AstCReset{varp->fileline(), vrefp});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,9 @@ private:
|
||||||
virtual void visit(AstVarRef* nodep) override {
|
virtual void visit(AstVarRef* nodep) override {
|
||||||
const AstNode* const backp = nodep->backp();
|
const AstNode* const backp = nodep->backp();
|
||||||
if (nodep->access().isReadOnly() && !VN_IS(backp, CCast) && VN_IS(backp, NodeMath)
|
if (nodep->access().isReadOnly() && !VN_IS(backp, CCast) && VN_IS(backp, NodeMath)
|
||||||
&& !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor) && backp->width()
|
&& !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor)
|
||||||
&& castSize(nodep) != castSize(nodep->varp())) {
|
&& (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
|
// Cast vars to IData first, else below has upper bits wrongly set
|
||||||
// CData x=3; out = (QData)(x<<30);
|
// CData x=3; out = (QData)(x<<30);
|
||||||
insertCast(nodep, castSize(nodep));
|
insertCast(nodep, castSize(nodep));
|
||||||
|
|
|
||||||
|
|
@ -646,8 +646,8 @@ private:
|
||||||
UINFO(4, " BLOCK " << nodep << endl);
|
UINFO(4, " BLOCK " << nodep << endl);
|
||||||
AstNode::user2ClearTree();
|
AstNode::user2ClearTree();
|
||||||
m_domainp = nodep->sensesp();
|
m_domainp = nodep->sensesp();
|
||||||
if (!m_domainp || m_domainp->hasCombo()
|
// Ignore static initializers, initial and final blocks
|
||||||
|| m_domainp->hasClocked()) { // IE not hasSettle/hasInitial
|
if (!m_domainp || m_domainp->hasCombo() || m_domainp->hasClocked()) {
|
||||||
iterateNewStmt(nodep);
|
iterateNewStmt(nodep);
|
||||||
}
|
}
|
||||||
m_domainp = nullptr;
|
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 "V3Global.h"
|
||||||
#include "V3Clock.h"
|
#include "V3Clock.h"
|
||||||
#include "V3Ast.h"
|
#include "V3Ast.h"
|
||||||
|
#include "V3Sched.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
|
@ -69,116 +70,19 @@ public:
|
||||||
|
|
||||||
class ClockVisitor final : public VNVisitor {
|
class ClockVisitor final : public VNVisitor {
|
||||||
private:
|
private:
|
||||||
// NODE STATE
|
|
||||||
// Cleared each Module:
|
|
||||||
// AstVarScope::user1p() -> AstVarScope*. Temporary signal that was created.
|
|
||||||
const VNUser1InUse m_inuser1;
|
|
||||||
|
|
||||||
// STATE
|
// 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.
|
AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates.
|
||||||
AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under
|
AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under
|
||||||
AstMTaskBody* m_mtaskBodyp = nullptr; // Current mtask body
|
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
VL_DEBUG_FUNC; // Declare debug()
|
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) {
|
AstNode* createSenseEquation(AstSenItem* nodesp) {
|
||||||
// Nodep may be a list of elements; we need to walk it
|
|
||||||
AstNode* senEqnp = nullptr;
|
AstNode* senEqnp = nullptr;
|
||||||
for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) {
|
for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) {
|
||||||
AstNode* const senOnep = createSenItemEquation(senp);
|
UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered");
|
||||||
if (senEqnp) {
|
AstNode* const senOnep = senp->sensp()->cloneTree(false);
|
||||||
// Add new OR to the sensitivity list equation
|
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
|
||||||
senEqnp = new AstOr(senp->fileline(), senEqnp, senOnep);
|
|
||||||
} else {
|
|
||||||
senEqnp = senOnep;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return senEqnp;
|
return senEqnp;
|
||||||
}
|
}
|
||||||
|
|
@ -192,116 +96,7 @@ private:
|
||||||
m_lastSenp = nullptr;
|
m_lastSenp = nullptr;
|
||||||
m_lastIfp = 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
|
// 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 {
|
virtual void visit(AstCoverToggle* nodep) override {
|
||||||
// nodep->dumpTree(cout, "ct:");
|
// nodep->dumpTree(cout, "ct:");
|
||||||
// COVERTOGGLE(INC, ORIG, CHANGE) ->
|
// COVERTOGGLE(INC, ORIG, CHANGE) ->
|
||||||
|
|
@ -319,110 +114,44 @@ private:
|
||||||
nodep->replaceWith(newp);
|
nodep->replaceWith(newp);
|
||||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
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 {
|
virtual void visit(AstSenTree* nodep) override {
|
||||||
// Delete it later; Actives still pointing to it
|
|
||||||
nodep->unlinkFrBack();
|
nodep->unlinkFrBack();
|
||||||
pushDeletep(nodep);
|
pushDeletep(nodep); // Delete it later, AstActives still pointing to it
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
virtual void visit(AstActive* nodep) override {
|
virtual void visit(AstActive* nodep) override {
|
||||||
// Careful if adding variables here, ACTIVES can be under other ACTIVES
|
// Careful if adding variables here, ACTIVES can be under other ACTIVES
|
||||||
// Need to save and restore any member state in AstUntilStable block
|
// Need to save and restore any member state in AstUntilStable block
|
||||||
if (!m_topScopep || !nodep->stmtsp()) {
|
VNRelinker relinker;
|
||||||
// Not at the top or empty block...
|
nodep->unlinkFrBack(&relinker);
|
||||||
// Only empty blocks should be leftover on the non-top. Killem.
|
UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty");
|
||||||
UASSERT_OBJ(!nodep->stmtsp(), nodep, "Non-empty lower active");
|
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
if (nodep->hasClocked()) {
|
||||||
} else if (m_mtaskBodyp) {
|
// Create 'if' statement, if needed
|
||||||
UINFO(4, " TR ACTIVE " << nodep << endl);
|
if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) {
|
||||||
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.
|
|
||||||
clearLastSen();
|
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 {
|
} else {
|
||||||
UINFO(4, " ACTIVE " << nodep << endl);
|
nodep->v3fatalSrc("Should have been removed by V3Sched::schedule");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||||
}
|
}
|
||||||
virtual void visit(AstExecGraph* nodep) override {
|
virtual void visit(AstExecGraph* nodep) override {
|
||||||
VL_RESTORER(m_mtaskBodyp);
|
for (AstMTaskBody* mtaskBodyp = nodep->mTaskBodiesp(); mtaskBodyp;
|
||||||
for (m_mtaskBodyp = nodep->mTaskBodiesp(); m_mtaskBodyp;
|
mtaskBodyp = VN_AS(mtaskBodyp->nextp(), MTaskBody)) {
|
||||||
m_mtaskBodyp = VN_AS(m_mtaskBodyp->nextp(), MTaskBody)) {
|
|
||||||
clearLastSen();
|
clearLastSen();
|
||||||
iterate(m_mtaskBodyp);
|
iterate(mtaskBodyp);
|
||||||
}
|
}
|
||||||
clearLastSen();
|
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:
|
public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
explicit ClockVisitor(AstNetlist* nodep) {
|
explicit ClockVisitor(AstNetlist* netlistp) { iterate(netlistp); }
|
||||||
iterate(nodep);
|
|
||||||
// Allow downstream modules to find _eval()
|
|
||||||
// easily without iterating through the tree.
|
|
||||||
nodep->evalp(m_evalFuncp);
|
|
||||||
}
|
|
||||||
virtual ~ClockVisitor() override = default;
|
virtual ~ClockVisitor() override = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2595,7 +2595,14 @@ private:
|
||||||
// Constants in sensitivity lists may be removed (we'll simplify later)
|
// Constants in sensitivity lists may be removed (we'll simplify later)
|
||||||
if (nodep->isClocked()) { // A constant can never get a pos/negedge
|
if (nodep->isClocked()) { // A constant can never get a pos/negedge
|
||||||
if (onlySenItemInSenTree(nodep)) {
|
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);
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||||
} else {
|
} else {
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||||
|
|
@ -2619,12 +2626,6 @@ private:
|
||||||
UASSERT_OBJ(senvarp, sensp, "Non-varref sensitivity variable");
|
UASSERT_OBJ(senvarp, sensp, "Non-varref sensitivity variable");
|
||||||
sensp->replaceWith(senvarp);
|
sensp->replaceWith(senvarp);
|
||||||
VL_DO_DANGLING(sensp->deleteTree(), sensp);
|
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 true;
|
||||||
if (lhsp->type() > rhsp->type()) return false;
|
if (lhsp->type() > rhsp->type()) return false;
|
||||||
// Looks visually better if we keep sorted by name
|
// Looks visually better if we keep sorted by name
|
||||||
if (!lhsp->varrefp() && rhsp->varrefp()) return true;
|
if (!lhsp->sensp() && rhsp->sensp()) return true;
|
||||||
if (lhsp->varrefp() && !rhsp->varrefp()) return false;
|
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() && rhsp->varrefp()) {
|
||||||
if (lhsp->varrefp()->name() < rhsp->varrefp()->name()) return true;
|
if (lhsp->varrefp()->name() < rhsp->varrefp()->name()) return true;
|
||||||
if (lhsp->varrefp()->name() > rhsp->varrefp()->name()) return false;
|
if (lhsp->varrefp()->name() > rhsp->varrefp()->name()) return false;
|
||||||
|
|
@ -2644,6 +2647,27 @@ private:
|
||||||
// Or rarely, different data types
|
// Or rarely, different data types
|
||||||
if (lhsp->varrefp()->dtypep() < rhsp->varrefp()->dtypep()) return true;
|
if (lhsp->varrefp()->dtypep() < rhsp->varrefp()->dtypep()) return true;
|
||||||
if (lhsp->varrefp()->dtypep() > rhsp->varrefp()->dtypep()) return false;
|
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.
|
// Sort by edge, AFTER variable, as we want multiple edges for same var adjacent.
|
||||||
// note the SenTree optimizer requires this order (more
|
// note the SenTree optimizer requires this order (more
|
||||||
|
|
@ -2706,17 +2730,13 @@ private:
|
||||||
AstSenItem* const litemp = senp;
|
AstSenItem* const litemp = senp;
|
||||||
AstSenItem* const ritemp = nextp;
|
AstSenItem* const ritemp = nextp;
|
||||||
if (ritemp) {
|
if (ritemp) {
|
||||||
if ((litemp->varrefp() && ritemp->varrefp()
|
if ((litemp->sensp() && ritemp->sensp()
|
||||||
&& litemp->varrefp()->sameGateTree(ritemp->varrefp()))
|
&& litemp->sensp()->sameGateTree(ritemp->sensp()))
|
||||||
|| (!litemp->varrefp() && !ritemp->varrefp())) {
|
|| (!litemp->sensp() && !ritemp->sensp())) {
|
||||||
// We've sorted in the order ANY, BOTH, POS, NEG,
|
// We've sorted in the order ANY, BOTH, POS, NEG,
|
||||||
// so we don't need to try opposite orders
|
// so we don't need to try opposite orders
|
||||||
if ((litemp->edgeType()
|
if ((litemp->edgeType() == VEdgeType::ET_POSEDGE // POS or NEG -> BOTH
|
||||||
== VEdgeType::ET_ANYEDGE) // ANY or {BOTH|POS|NEG} -> ANY
|
&& ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
|
||||||
|| (litemp->edgeType()
|
|
||||||
== VEdgeType::ET_BOTHEDGE) // BOTH or {POS|NEG} -> BOTH
|
|
||||||
|| (litemp->edgeType() == VEdgeType::ET_POSEDGE // POS or NEG -> BOTH
|
|
||||||
&& ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
|
|
||||||
|| (litemp->edgeType() == ritemp->edgeType()) // Identical edges
|
|| (litemp->edgeType() == ritemp->edgeType()) // Identical edges
|
||||||
) {
|
) {
|
||||||
// Fix edge of old node
|
// Fix edge of old node
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ private:
|
||||||
// Class packages might have no children, but need to remain as
|
// Class packages might have no children, but need to remain as
|
||||||
// long as the class they refer to is needed
|
// long as the class they refer to is needed
|
||||||
if (VN_IS(m_modp, Class) || VN_IS(m_modp, ClassPackage)) nodep->user1Inc();
|
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);
|
m_scopesp.push_back(nodep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ private:
|
||||||
AstAssignDly* m_nextDlyp = nullptr; // Next delayed assignment in a list of assignments
|
AstAssignDly* m_nextDlyp = nullptr; // Next delayed assignment in a list of assignments
|
||||||
bool m_inDly = false; // True in delayed assignments
|
bool m_inDly = false; // True in delayed assignments
|
||||||
bool m_inLoop = false; // True in for loops
|
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*>;
|
using VarMap = std::map<const std::pair<AstNodeModule*, std::string>, AstVar*>;
|
||||||
VarMap m_modVarMap; // Table of new var names created under module
|
VarMap m_modVarMap; // Table of new var names created under module
|
||||||
VDouble0 m_statSharedSet; // Statistic tracking
|
VDouble0 m_statSharedSet; // Statistic tracking
|
||||||
|
|
@ -160,7 +160,7 @@ private:
|
||||||
return varscp;
|
return varscp;
|
||||||
}
|
}
|
||||||
|
|
||||||
AstActive* createActivePost(AstVarRef* varrefp) {
|
AstActive* createActive(AstNode* varrefp) {
|
||||||
AstActive* const newactp
|
AstActive* const newactp
|
||||||
= new AstActive(varrefp->fileline(), "sequentdly", m_activep->sensesp());
|
= new AstActive(varrefp->fileline(), "sequentdly", m_activep->sensesp());
|
||||||
// Was addNext(), but addNextHere() avoids a linear search.
|
// Was addNext(), but addNextHere() avoids a linear search.
|
||||||
|
|
@ -336,7 +336,7 @@ private:
|
||||||
} else { // first time we've dealt with this memory
|
} else { // first time we've dealt with this memory
|
||||||
finalp = new AstAlwaysPost(nodep->fileline(), nullptr /*sens*/, nullptr /*body*/);
|
finalp = new AstAlwaysPost(nodep->fileline(), nullptr /*sens*/, nullptr /*body*/);
|
||||||
UINFO(9, " Created " << finalp << endl);
|
UINFO(9, " Created " << finalp << endl);
|
||||||
AstActive* const newactp = createActivePost(varrefp);
|
AstActive* const newactp = createActive(varrefp);
|
||||||
newactp->addStmtsp(finalp);
|
newactp->addStmtsp(finalp);
|
||||||
varrefp->varScopep()->user4p(finalp);
|
varrefp->varScopep()->user4p(finalp);
|
||||||
finalp->user2p(newactp);
|
finalp->user2p(newactp);
|
||||||
|
|
@ -383,12 +383,54 @@ private:
|
||||||
m_activep = nodep;
|
m_activep = nodep;
|
||||||
VL_RESTORER(m_inInitial);
|
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.
|
// Two sets to same variable in different actives must use different vars.
|
||||||
AstNode::user3ClearTree();
|
AstNode::user3ClearTree();
|
||||||
iterateChildren(nodep);
|
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 {
|
virtual void visit(AstAssignDly* nodep) override {
|
||||||
m_inDly = true;
|
m_inDly = true;
|
||||||
m_nextDlyp
|
m_nextDlyp
|
||||||
|
|
@ -407,7 +449,7 @@ private:
|
||||||
"loops (non-delayed is ok - see docs)");
|
"loops (non-delayed is ok - see docs)");
|
||||||
}
|
}
|
||||||
const AstBasicDType* const basicp = lhsp->dtypep()->basicp();
|
const AstBasicDType* const basicp = lhsp->dtypep()->basicp();
|
||||||
if (basicp && basicp->isEventValue()) {
|
if (basicp && basicp->isEvent()) {
|
||||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays");
|
nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays");
|
||||||
}
|
}
|
||||||
if (newlhsp) {
|
if (newlhsp) {
|
||||||
|
|
@ -444,20 +486,10 @@ private:
|
||||||
if (!dlyvscp) { // First use of this delayed variable
|
if (!dlyvscp) { // First use of this delayed variable
|
||||||
const string newvarname = (string("__Vdly__") + nodep->varp()->shortName());
|
const string newvarname = (string("__Vdly__") + nodep->varp()->shortName());
|
||||||
dlyvscp = createVarSc(oldvscp, newvarname, 0, nullptr);
|
dlyvscp = createVarSc(oldvscp, newvarname, 0, nullptr);
|
||||||
AstNodeAssign* prep;
|
AstNodeAssign* const prep = new AstAssignPre(
|
||||||
const AstBasicDType* const basicp = oldvscp->dtypep()->basicp();
|
nodep->fileline(),
|
||||||
if (basicp && basicp->isEventValue()) {
|
new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE),
|
||||||
// Events go to zero on next timestep unless reactivated
|
new AstVarRef(nodep->fileline(), oldvscp, VAccess::READ));
|
||||||
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 postp = new AstAssignPost(
|
AstNodeAssign* const postp = new AstAssignPost(
|
||||||
nodep->fileline(),
|
nodep->fileline(),
|
||||||
new AstVarRef(nodep->fileline(), oldvscp, VAccess::WRITE),
|
new AstVarRef(nodep->fileline(), oldvscp, VAccess::WRITE),
|
||||||
|
|
@ -465,7 +497,7 @@ private:
|
||||||
postp->lhsp()->user2(true); // Don't detect this assignment
|
postp->lhsp()->user2(true); // Don't detect this assignment
|
||||||
oldvscp->user1p(dlyvscp); // So we can find it later
|
oldvscp->user1p(dlyvscp); // So we can find it later
|
||||||
// Make new ACTIVE with identical sensitivity tree
|
// Make new ACTIVE with identical sensitivity tree
|
||||||
AstActive* const newactp = createActivePost(nodep);
|
AstActive* const newactp = createActive(nodep);
|
||||||
dlyvscp->user2p(newactp);
|
dlyvscp->user2p(newactp);
|
||||||
newactp->addStmtsp(prep); // Add to FRONT of statements
|
newactp->addStmtsp(prep); // Add to FRONT of statements
|
||||||
newactp->addStmtsp(postp);
|
newactp->addStmtsp(postp);
|
||||||
|
|
|
||||||
|
|
@ -731,80 +731,3 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
|
||||||
}
|
}
|
||||||
return "";
|
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_labelNum = 0; // Next label number
|
||||||
int m_splitSize = 0; // # of cfunc nodes placed into output file
|
int m_splitSize = 0; // # of cfunc nodes placed into output file
|
||||||
bool m_inUC = false; // Inside an AstUCStmt or AstUCMath
|
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
|
bool m_emitConstInit = false; // Emitting constant initializer
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -177,7 +176,6 @@ public:
|
||||||
void emitVarReset(AstVar* varp);
|
void emitVarReset(AstVar* varp);
|
||||||
string emitVarResetRecurse(const AstVar* varp, const string& varNameProtected,
|
string emitVarResetRecurse(const AstVar* varp, const string& varNameProtected,
|
||||||
AstNodeDType* dtypep, int depth, const string& suffix);
|
AstNodeDType* dtypep, int depth, const string& suffix);
|
||||||
void doubleOrDetect(AstChangeDet* changep, bool& gotOne);
|
|
||||||
void emitChangeDet();
|
void emitChangeDet();
|
||||||
void emitConstInit(AstNode* initp) {
|
void emitConstInit(AstNode* initp) {
|
||||||
// We should refactor emit to produce output into a provided buffer, not go through members
|
// We should refactor emit to produce output into a provided buffer, not go through members
|
||||||
|
|
@ -194,8 +192,6 @@ public:
|
||||||
VL_RESTORER(m_cfuncp);
|
VL_RESTORER(m_cfuncp);
|
||||||
m_cfuncp = nodep;
|
m_cfuncp = nodep;
|
||||||
|
|
||||||
m_blkChangeDetVec.clear();
|
|
||||||
|
|
||||||
splitSizeInc(nodep);
|
splitSizeInc(nodep);
|
||||||
|
|
||||||
puts("\n");
|
puts("\n");
|
||||||
|
|
@ -244,15 +240,11 @@ public:
|
||||||
iterateAndNextNull(nodep->stmtsp());
|
iterateAndNextNull(nodep->stmtsp());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_blkChangeDetVec.empty()) emitChangeDet();
|
|
||||||
|
|
||||||
if (nodep->finalsp()) {
|
if (nodep->finalsp()) {
|
||||||
putsDecoration("// Final\n");
|
putsDecoration("// Final\n");
|
||||||
iterateAndNextNull(nodep->finalsp());
|
iterateAndNextNull(nodep->finalsp());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_blkChangeDetVec.empty()) puts("return __req;\n");
|
|
||||||
|
|
||||||
puts("}\n");
|
puts("}\n");
|
||||||
if (nodep->ifdef() != "") puts("#endif // " + nodep->ifdef() + "\n");
|
if (nodep->ifdef() != "") puts("#endif // " + nodep->ifdef() + "\n");
|
||||||
}
|
}
|
||||||
|
|
@ -1207,9 +1199,6 @@ public:
|
||||||
UASSERT_OBJ(!nodep->mTaskBodiesp(), nodep, "These should have been lowered");
|
UASSERT_OBJ(!nodep->mTaskBodiesp(), nodep, "These should have been lowered");
|
||||||
iterateChildrenConst(nodep);
|
iterateChildrenConst(nodep);
|
||||||
}
|
}
|
||||||
virtual void visit(AstChangeDet* nodep) override { //
|
|
||||||
m_blkChangeDetVec.push_back(nodep);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default
|
// Default
|
||||||
virtual void visit(AstNode* nodep) override {
|
virtual void visit(AstNode* nodep) override {
|
||||||
|
|
|
||||||
|
|
@ -372,6 +372,7 @@ class EmitCImp final : EmitCFunc {
|
||||||
// lower level subinst code does it.
|
// lower level subinst code does it.
|
||||||
} else if (varp->isParam()) {
|
} else if (varp->isParam()) {
|
||||||
} else if (varp->isStatic() && varp->isConst()) {
|
} else if (varp->isStatic() && varp->isConst()) {
|
||||||
|
} else if (varp->basicp() && varp->basicp()->isTriggerVec()) {
|
||||||
} else {
|
} else {
|
||||||
int vects = 0;
|
int vects = 0;
|
||||||
AstNodeDType* elementp = varp->dtypeSkipRefp();
|
AstNodeDType* elementp = varp->dtypeSkipRefp();
|
||||||
|
|
|
||||||
|
|
@ -314,97 +314,25 @@ class EmitCModel final : public EmitCFunc {
|
||||||
puts("}\n");
|
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) {
|
void emitStandardMethods1(AstNodeModule* modp) {
|
||||||
UASSERT_OBJ(modp->isTop(), modp, "Attempting to emitWrapEval for non-top class");
|
UASSERT_OBJ(modp->isTop(), modp, "Attempting to emitWrapEval for non-top class");
|
||||||
|
|
||||||
const string topModNameProtected = prefixNameProtect(modp);
|
const string topModNameProtected = prefixNameProtect(modp);
|
||||||
const string selfDecl = "(" + topModNameProtected + "* vlSelf)";
|
const string selfDecl = "(" + topModNameProtected + "* vlSelf)";
|
||||||
|
|
||||||
putSectionDelimiter("Evaluation loop");
|
putSectionDelimiter("Evaluation function");
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
puts("\n");
|
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("#ifdef VL_DEBUG\n");
|
||||||
puts("void " + topModNameProtected + "__" + protect("_eval_debug_assertions") + selfDecl
|
puts("void " + topModNameProtected + "__" + protect("_eval_debug_assertions") + selfDecl
|
||||||
+ ";\n");
|
+ ";\n");
|
||||||
puts("#endif // VL_DEBUG\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
|
// ::eval_step
|
||||||
puts("\nvoid " + topClassName() + "::eval_step() {\n");
|
puts("\nvoid " + topClassName() + "::eval_step() {\n");
|
||||||
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName()
|
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName()
|
||||||
|
|
@ -416,9 +344,17 @@ class EmitCModel final : public EmitCFunc {
|
||||||
+ "(&(vlSymsp->TOP));\n");
|
+ "(&(vlSymsp->TOP));\n");
|
||||||
puts("#endif // VL_DEBUG\n");
|
puts("#endif // VL_DEBUG\n");
|
||||||
|
|
||||||
putsDecoration("// Initialize\n");
|
if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n");
|
||||||
puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) " + protect("_eval_initial_loop")
|
|
||||||
+ "(vlSymsp);\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) {
|
if (v3Global.opt.threads() == 1) {
|
||||||
const uint32_t mtaskId = 0;
|
const uint32_t mtaskId = 0;
|
||||||
|
|
@ -432,7 +368,8 @@ class EmitCModel final : public EmitCFunc {
|
||||||
puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalBegin();\n");
|
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");
|
putsDecoration("// Evaluate cleanup\n");
|
||||||
if (v3Global.opt.threads() == 1) {
|
if (v3Global.opt.threads() == 1) {
|
||||||
|
|
@ -444,8 +381,10 @@ class EmitCModel final : public EmitCFunc {
|
||||||
puts("}\n");
|
puts("}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitStandardMethods3(AstNodeModule* modp) {
|
void emitStandardMethods2(AstNodeModule* modp) {
|
||||||
const string topModNameProtected = prefixNameProtect(modp);
|
const string topModNameProtected = prefixNameProtect(modp);
|
||||||
|
const string selfDecl = "(" + topModNameProtected + "* vlSelf)";
|
||||||
|
|
||||||
// ::eval_end_step
|
// ::eval_end_step
|
||||||
if (v3Global.needTraceDumper() && !optSystemC()) {
|
if (v3Global.needTraceDumper() && !optSystemC()) {
|
||||||
puts("\nvoid " + topClassName() + "::eval_end_step() {\n");
|
puts("\nvoid " + topClassName() + "::eval_end_step() {\n");
|
||||||
|
|
@ -473,9 +412,12 @@ class EmitCModel final : public EmitCFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
putSectionDelimiter("Invoke final blocks");
|
putSectionDelimiter("Invoke final blocks");
|
||||||
|
// Forward declarations
|
||||||
|
puts("\n");
|
||||||
|
puts("void " + topModNameProtected + "__" + protect("_eval_final") + selfDecl + ";\n");
|
||||||
// ::final
|
// ::final
|
||||||
puts("\nVL_ATTR_COLD void " + topClassName() + "::final() {\n");
|
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");
|
puts("}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -599,7 +541,6 @@ class EmitCModel final : public EmitCFunc {
|
||||||
emitDestructorImplementation();
|
emitDestructorImplementation();
|
||||||
emitStandardMethods1(modp);
|
emitStandardMethods1(modp);
|
||||||
emitStandardMethods2(modp);
|
emitStandardMethods2(modp);
|
||||||
emitStandardMethods3(modp);
|
|
||||||
if (v3Global.opt.trace()) { emitTraceMethods(modp); }
|
if (v3Global.opt.trace()) { emitTraceMethods(modp); }
|
||||||
if (v3Global.opt.savable()) { emitSerializationFunctions(); }
|
if (v3Global.opt.savable()) { emitSerializationFunctions(); }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -443,6 +443,7 @@ void EmitCSyms::emitSymHdr() {
|
||||||
puts("uint32_t __Vm_baseCode = 0;"
|
puts("uint32_t __Vm_baseCode = 0;"
|
||||||
" ///< Used by trace routines when tracing multiple models\n");
|
" ///< 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");
|
puts("bool __Vm_didInit = false;\n");
|
||||||
|
|
||||||
if (v3Global.opt.profExec()) {
|
if (v3Global.opt.profExec()) {
|
||||||
|
|
@ -506,6 +507,22 @@ void EmitCSyms::emitSymHdr() {
|
||||||
puts("\n// METHODS\n");
|
puts("\n// METHODS\n");
|
||||||
puts("const char* name() { return TOP.name(); }\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 (v3Global.needTraceDumper()) {
|
||||||
if (!optSystemC()) puts("void _traceDump();\n");
|
if (!optSystemC()) puts("void _traceDump();\n");
|
||||||
puts("void _traceDumpOpen();\n");
|
puts("void _traceDumpOpen();\n");
|
||||||
|
|
|
||||||
|
|
@ -421,6 +421,16 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor {
|
||||||
puts(")");
|
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
|
// Operators
|
||||||
virtual void emitVerilogFormat(AstNode* nodep, const string& format, AstNode* lhsp = nullptr,
|
virtual void emitVerilogFormat(AstNode* nodep, const string& format, AstNode* lhsp = nullptr,
|
||||||
AstNode* const rhsp = nullptr, AstNode* thsp = nullptr,
|
AstNode* const rhsp = nullptr, AstNode* thsp = nullptr,
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,6 @@ class EmitXmlFileVisitor final : public VNVisitor {
|
||||||
} else if (nodep->attrClocker() == VVarAttrClocker::CLOCKER_NO) {
|
} else if (nodep->attrClocker() == VVarAttrClocker::CLOCKER_NO) {
|
||||||
puts(" clocker=\"false\"");
|
puts(" clocker=\"false\"");
|
||||||
}
|
}
|
||||||
if (nodep->attrClockEn()) puts(" clock_enable=\"true\"");
|
|
||||||
if (nodep->attrIsolateAssign()) puts(" isolate_assignments=\"true\"");
|
if (nodep->attrIsolateAssign()) puts(" isolate_assignments=\"true\"");
|
||||||
if (nodep->isLatched()) puts(" latched=\"true\"");
|
if (nodep->isLatched()) puts(" latched=\"true\"");
|
||||||
if (nodep->isSigPublic()) puts(" public=\"true\"");
|
if (nodep->isSigPublic()) puts(" public=\"true\"");
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ public:
|
||||||
CASEX, // Casex
|
CASEX, // Casex
|
||||||
CASTCONST, // Cast is constant
|
CASTCONST, // Cast is constant
|
||||||
CDCRSTLOGIC, // Logic in async reset path
|
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
|
CMPCONST, // Comparison is constant due to limited range
|
||||||
COLONPLUS, // :+ instead of +:
|
COLONPLUS, // :+ instead of +:
|
||||||
COMBDLY, // Combinatorial delayed assignment
|
COMBDLY, // Combinatorial delayed assignment
|
||||||
|
|
@ -85,11 +85,11 @@ public:
|
||||||
DEPRECATED, // Feature will be deprecated
|
DEPRECATED, // Feature will be deprecated
|
||||||
ENDLABEL, // End lable name mismatch
|
ENDLABEL, // End lable name mismatch
|
||||||
EOFNEWLINE, // End-of-file missing newline
|
EOFNEWLINE, // End-of-file missing newline
|
||||||
GENCLK, // Generated Clock
|
GENCLK, // Generated Clock. Historical, never issued.
|
||||||
HIERBLOCK, // Ignored hierarchical block setting
|
HIERBLOCK, // Ignored hierarchical block setting
|
||||||
IFDEPTH, // If statements too deep
|
IFDEPTH, // If statements too deep
|
||||||
IGNOREDRETURN, // Ignoring return value (function as task)
|
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
|
IMPLICIT, // Implicit wire
|
||||||
IMPORTSTAR, // Import::* in $unit
|
IMPORTSTAR, // Import::* in $unit
|
||||||
IMPURE, // Impure function not being inlined
|
IMPURE, // Impure function not being inlined
|
||||||
|
|
@ -124,7 +124,7 @@ public:
|
||||||
TICKCOUNT, // Too large tick count
|
TICKCOUNT, // Too large tick count
|
||||||
TIMESCALEMOD, // Need timescale for module
|
TIMESCALEMOD, // Need timescale for module
|
||||||
UNDRIVEN, // No drivers
|
UNDRIVEN, // No drivers
|
||||||
UNOPT, // Unoptimizable block
|
UNOPT, // Unoptimizable block. Historical, never issued.
|
||||||
UNOPTFLAT, // Unoptimizable block after flattening
|
UNOPTFLAT, // Unoptimizable block after flattening
|
||||||
UNOPTTHREADS, // Thread partitioner unable to fill all requested threads
|
UNOPTTHREADS, // Thread partitioner unable to fill all requested threads
|
||||||
UNPACKED, // Unsupported unpacked
|
UNPACKED, // Unsupported unpacked
|
||||||
|
|
|
||||||
|
|
@ -324,8 +324,6 @@ void FileLine::warnStyleOff(bool flag) {
|
||||||
bool FileLine::warnIsOff(V3ErrorCode code) const {
|
bool FileLine::warnIsOff(V3ErrorCode code) const {
|
||||||
if (!m_warnOn.test(code)) return true;
|
if (!m_warnOn.test(code)) return true;
|
||||||
if (!defaultFileLine().m_warnOn.test(code)) return true; // Global overrides local
|
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)) {
|
if ((code.lintError() || code.styleError()) && !m_warnOn.test(V3ErrorCode::I_LINT)) {
|
||||||
return true;
|
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)
|
// Experimenting with always requiring heavy, see (#2701)
|
||||||
bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols
|
bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols
|
||||||
bool m_dpi = false; // Need __Dpi include files
|
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_hasForceableSignals = false; // Need to apply V3Force pass
|
||||||
bool m_hasSCTextSections = false; // Has `systemc_* sections that need to be emitted
|
bool m_hasSCTextSections = false; // Has `systemc_* sections that need to be emitted
|
||||||
bool m_useParallelBuild = false; // Use parallel build for model
|
bool m_useParallelBuild = false; // Use parallel build for model
|
||||||
|
|
@ -148,6 +149,8 @@ public:
|
||||||
void needTraceDumper(bool flag) { m_needTraceDumper = flag; }
|
void needTraceDumper(bool flag) { m_needTraceDumper = flag; }
|
||||||
bool dpi() const { return m_dpi; }
|
bool dpi() const { return m_dpi; }
|
||||||
void dpi(bool flag) { m_dpi = flag; }
|
void dpi(bool flag) { m_dpi = flag; }
|
||||||
|
bool hasEvents() const { return m_hasEvents; }
|
||||||
|
void setHasEvents() { m_hasEvents = true; }
|
||||||
bool hasForceableSignals() const { return m_hasForceableSignals; }
|
bool hasForceableSignals() const { return m_hasForceableSignals; }
|
||||||
void setHasForceableSignals() { m_hasForceableSignals = true; }
|
void setHasForceableSignals() { m_hasForceableSignals = true; }
|
||||||
bool hasSCTextSections() const { return m_hasSCTextSections; }
|
bool hasSCTextSections() const { return m_hasSCTextSections; }
|
||||||
|
|
|
||||||
|
|
@ -131,8 +131,8 @@ struct LifePostLocation {
|
||||||
class LifePostDlyVisitor final : public VNVisitor {
|
class LifePostDlyVisitor final : public VNVisitor {
|
||||||
private:
|
private:
|
||||||
// NODE STATE
|
// NODE STATE
|
||||||
// Cleared on entire tree
|
// AstVarScope::user1() -> bool: referenced outside _eval__nba
|
||||||
// AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var
|
// AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var
|
||||||
const VNUser4InUse m_inuser4;
|
const VNUser4InUse m_inuser4;
|
||||||
|
|
||||||
// STATE
|
// STATE
|
||||||
|
|
@ -155,6 +155,9 @@ private:
|
||||||
const V3Graph* m_mtasksGraphp = nullptr; // Mtask tracking graph
|
const V3Graph* m_mtasksGraphp = nullptr; // Mtask tracking graph
|
||||||
std::unique_ptr<GraphPathChecker> m_checker;
|
std::unique_ptr<GraphPathChecker> m_checker;
|
||||||
|
|
||||||
|
const AstCFunc* const m_evalNbap; // The _eval__nba function
|
||||||
|
bool m_inEvalNba = false; // Traversing under _eval__nba
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
VL_DEBUG_FUNC; // Declare debug()
|
VL_DEBUG_FUNC; // Declare debug()
|
||||||
|
|
||||||
|
|
@ -185,8 +188,11 @@ private:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void squashAssignposts() {
|
void squashAssignposts() {
|
||||||
for (auto& itr : m_assignposts) {
|
for (auto& pair : m_assignposts) {
|
||||||
const LifePostLocation* const app = &itr.second;
|
// 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 lhsp = VN_AS(app->nodep->lhsp(), VarRef); // original var
|
||||||
const AstVarRef* const rhsp = VN_AS(app->nodep->rhsp(), VarRef); // dly var
|
const AstVarRef* const rhsp = VN_AS(app->nodep->rhsp(), VarRef); // dly var
|
||||||
AstVarScope* const dlyVarp = rhsp->varScopep();
|
AstVarScope* const dlyVarp = rhsp->varScopep();
|
||||||
|
|
@ -274,6 +280,12 @@ private:
|
||||||
LifePostElimVisitor{nodep};
|
LifePostElimVisitor{nodep};
|
||||||
}
|
}
|
||||||
virtual void visit(AstVarRef* nodep) override {
|
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,
|
// Consumption/generation of a variable,
|
||||||
const AstVarScope* const vscp = nodep->varScopep();
|
const AstVarScope* const vscp = nodep->varScopep();
|
||||||
UASSERT_OBJ(vscp, nodep, "Scope not assigned");
|
UASSERT_OBJ(vscp, nodep, "Scope not assigned");
|
||||||
|
|
@ -288,6 +300,7 @@ private:
|
||||||
// The pre-assignment into the dly var should not count as its
|
// The pre-assignment into the dly var should not count as its
|
||||||
// first write; we only want to consider reads and writes that
|
// first write; we only want to consider reads and writes that
|
||||||
// would still happen if the dly var were eliminated.
|
// would still happen if the dly var were eliminated.
|
||||||
|
if (!m_inEvalNba) iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
virtual void visit(AstAssignPost* nodep) override {
|
virtual void visit(AstAssignPost* nodep) override {
|
||||||
// Don't record ASSIGNPOST in the read/write maps, record them in a
|
// Don't record ASSIGNPOST in the read/write maps, record them in a
|
||||||
|
|
@ -315,9 +328,11 @@ private:
|
||||||
}
|
}
|
||||||
virtual void visit(AstExecGraph* nodep) override {
|
virtual void visit(AstExecGraph* nodep) override {
|
||||||
// Treat the ExecGraph like a call to each mtask body
|
// Treat the ExecGraph like a call to each mtask body
|
||||||
UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
|
if (m_inEvalNba) {
|
||||||
m_mtasksGraphp = nodep->depGraphp();
|
UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
|
||||||
for (V3GraphVertex* mtaskVxp = m_mtasksGraphp->verticesBeginp(); mtaskVxp;
|
m_mtasksGraphp = nodep->depGraphp();
|
||||||
|
}
|
||||||
|
for (V3GraphVertex* mtaskVxp = nodep->depGraphp()->verticesBeginp(); mtaskVxp;
|
||||||
mtaskVxp = mtaskVxp->verticesNextp()) {
|
mtaskVxp = mtaskVxp->verticesNextp()) {
|
||||||
const ExecMTask* const mtaskp = dynamic_cast<ExecMTask*>(mtaskVxp);
|
const ExecMTask* const mtaskp = dynamic_cast<ExecMTask*>(mtaskVxp);
|
||||||
m_execMTaskp = mtaskp;
|
m_execMTaskp = mtaskp;
|
||||||
|
|
@ -328,6 +343,8 @@ private:
|
||||||
}
|
}
|
||||||
virtual void visit(AstCFunc* nodep) override {
|
virtual void visit(AstCFunc* nodep) override {
|
||||||
if (!m_tracingCall && !nodep->entryPoint()) return;
|
if (!m_tracingCall && !nodep->entryPoint()) return;
|
||||||
|
VL_RESTORER(m_inEvalNba);
|
||||||
|
if (nodep == m_evalNbap) m_inEvalNba = true;
|
||||||
m_tracingCall = false;
|
m_tracingCall = false;
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
|
|
@ -337,7 +354,10 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
explicit LifePostDlyVisitor(AstNetlist* nodep) { iterate(nodep); }
|
explicit LifePostDlyVisitor(AstNetlist* netlistp)
|
||||||
|
: m_evalNbap{netlistp->evalNbap()} {
|
||||||
|
iterate(netlistp);
|
||||||
|
}
|
||||||
virtual ~LifePostDlyVisitor() override {
|
virtual ~LifePostDlyVisitor() override {
|
||||||
V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel);
|
V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,13 @@ private:
|
||||||
iterateAndNextNull(nodep->lhsp());
|
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 {
|
virtual void visit(AstCastDynamic* nodep) override {
|
||||||
VL_RESTORER(m_setRefLvalue);
|
VL_RESTORER(m_setRefLvalue);
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -305,7 +305,7 @@ private:
|
||||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||||
} else if (nodep->attrType() == VAttrType::VAR_CLOCK_ENABLE) {
|
} else if (nodep->attrType() == VAttrType::VAR_CLOCK_ENABLE) {
|
||||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
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);
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||||
} else if (nodep->attrType() == VAttrType::VAR_FORCEABLE) {
|
} else if (nodep->attrType() == VAttrType::VAR_FORCEABLE) {
|
||||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||||
|
|
|
||||||
|
|
@ -148,39 +148,8 @@ private:
|
||||||
virtual void visit(AstSenItem* nodep) override {
|
virtual void visit(AstSenItem* nodep) override {
|
||||||
// Remove bit selects, and bark if it's not a simple variable
|
// Remove bit selects, and bark if it's not a simple variable
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
if (nodep->isClocked()) {
|
if (!nodep->isClocked()) {
|
||||||
// If it's not a simple variable wrap in a temporary
|
// Old V1995 sensitivity list; we'll probably mostly ignore
|
||||||
// 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
|
|
||||||
bool did = true;
|
bool did = true;
|
||||||
while (did) {
|
while (did) {
|
||||||
did = false;
|
did = false;
|
||||||
|
|
@ -205,9 +174,7 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!VN_IS(nodep->sensp(), NodeVarRef)
|
if (nodep->isIllegal()) {
|
||||||
&& !VN_IS(nodep->sensp(), EnumItemRef) // V3Const will cleanup
|
|
||||||
&& !nodep->isIllegal()) {
|
|
||||||
if (debug()) nodep->dumpTree(cout, "-tree: ");
|
if (debug()) nodep->dumpTree(cout, "-tree: ");
|
||||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Complex statement in sensitivity list");
|
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("-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", Set, &m_outputSplit);
|
||||||
DECL_OPTION("-output-split-cfuncs", CbVal, [this, fl](const char* valp) {
|
DECL_OPTION("-output-split-cfuncs", CbVal, [this, fl](const char* valp) {
|
||||||
m_outputSplitCFuncs = std::atoi(valp);
|
m_outputSplitCFuncs = std::atoi(valp);
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,6 @@ private:
|
||||||
bool m_gmake = false; // main switch: --make gmake
|
bool m_gmake = false; // main switch: --make gmake
|
||||||
bool m_main = false; // main swithc: --main
|
bool m_main = false; // main swithc: --main
|
||||||
bool m_mergeConstPool = true; // main switch: --merge-const-pool
|
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_outFormatOk = false; // main switch: --cc, --sc or --sp was specified
|
||||||
bool m_pedantic = false; // main switch: --Wpedantic
|
bool m_pedantic = false; // main switch: --Wpedantic
|
||||||
bool m_pinsScUint = false; // main switch: --pins-sc-uint
|
bool m_pinsScUint = false; // main switch: --pins-sc-uint
|
||||||
|
|
@ -459,7 +458,6 @@ public:
|
||||||
bool traceUnderscore() const { return m_traceUnderscore; }
|
bool traceUnderscore() const { return m_traceUnderscore; }
|
||||||
bool main() const { return m_main; }
|
bool main() const { return m_main; }
|
||||||
bool mergeConstPool() const { return m_mergeConstPool; }
|
bool mergeConstPool() const { return m_mergeConstPool; }
|
||||||
bool orderClockDly() const { return m_orderClockDly; }
|
|
||||||
bool outFormatOk() const { return m_outFormatOk; }
|
bool outFormatOk() const { return m_outFormatOk; }
|
||||||
bool keepTempFiles() const { return (V3Error::debugDefault() != 0); }
|
bool keepTempFiles() const { return (V3Error::debugDefault() != 0); }
|
||||||
bool pedantic() const { return m_pedantic; }
|
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 "config_build.h"
|
||||||
#include "verilatedos.h"
|
#include "verilatedos.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class AstCFunc;
|
||||||
class AstNetlist;
|
class AstNetlist;
|
||||||
|
class AstSenItem;
|
||||||
|
class AstSenTree;
|
||||||
|
class AstVarScope;
|
||||||
|
|
||||||
|
namespace V3Sched {
|
||||||
|
struct LogicByScope;
|
||||||
|
};
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
class V3Order final {
|
namespace V3Order {
|
||||||
public:
|
|
||||||
static void orderAll(AstNetlist* nodep);
|
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
|
#endif // Guard
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
// OrderMoveVertex
|
// OrderMoveVertex
|
||||||
// MTaskMoveVertex
|
// MTaskMoveVertex
|
||||||
// OrderEitherVertex
|
// OrderEitherVertex
|
||||||
// OrderInputsVertex
|
|
||||||
// OrderLogicVertex
|
// OrderLogicVertex
|
||||||
// OrderVarVertex
|
// OrderVarVertex
|
||||||
// OrderVarStdVertex
|
// OrderVarStdVertex
|
||||||
|
|
@ -29,7 +28,6 @@
|
||||||
//
|
//
|
||||||
// V3GraphEdge
|
// V3GraphEdge
|
||||||
// OrderEdge
|
// OrderEdge
|
||||||
// OrderComboCutEdge
|
|
||||||
// OrderPostCutEdge
|
// OrderPostCutEdge
|
||||||
// OrderPreCutEdge
|
// OrderPreCutEdge
|
||||||
//*************************************************************************
|
//*************************************************************************
|
||||||
|
|
@ -53,7 +51,6 @@ class OrderMoveDomScope;
|
||||||
//######################################################################
|
//######################################################################
|
||||||
|
|
||||||
enum OrderWeights : uint8_t {
|
enum OrderWeights : uint8_t {
|
||||||
WEIGHT_INPUT = 1, // Low weight just so dot graph looks nice
|
|
||||||
WEIGHT_COMBO = 1, // Breakable combo logic
|
WEIGHT_COMBO = 1, // Breakable combo logic
|
||||||
WEIGHT_POST = 2, // Post-delayed used var
|
WEIGHT_POST = 2, // Post-delayed used var
|
||||||
WEIGHT_PRE = 3, // Breakable pre-delayed used var
|
WEIGHT_PRE = 3, // Breakable pre-delayed used var
|
||||||
|
|
@ -64,13 +61,11 @@ enum OrderWeights : uint8_t {
|
||||||
struct OrderVEdgeType {
|
struct OrderVEdgeType {
|
||||||
enum en : uint8_t {
|
enum en : uint8_t {
|
||||||
VERTEX_UNKNOWN = 0,
|
VERTEX_UNKNOWN = 0,
|
||||||
VERTEX_INPUTS,
|
|
||||||
VERTEX_LOGIC,
|
VERTEX_LOGIC,
|
||||||
VERTEX_VARSTD,
|
VERTEX_VARSTD,
|
||||||
VERTEX_VARPRE,
|
VERTEX_VARPRE,
|
||||||
VERTEX_VARPOST,
|
VERTEX_VARPOST,
|
||||||
VERTEX_VARPORD,
|
VERTEX_VARPORD,
|
||||||
VERTEX_VARSETTLE,
|
|
||||||
VERTEX_MOVE,
|
VERTEX_MOVE,
|
||||||
EDGE_STD,
|
EDGE_STD,
|
||||||
EDGE_COMBOCUT,
|
EDGE_COMBOCUT,
|
||||||
|
|
@ -80,10 +75,9 @@ struct OrderVEdgeType {
|
||||||
};
|
};
|
||||||
const char* ascii() const {
|
const char* ascii() const {
|
||||||
static const char* const names[]
|
static const char* const names[]
|
||||||
= {"%E-vedge", "VERTEX_INPUTS", "VERTEX_LOGIC", "VERTEX_VARSTD",
|
= {"%E-vedge", "VERTEX_LOGIC", "VERTEX_VARSTD", "VERTEX_VARPRE",
|
||||||
"VERTEX_VARPRE", "VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_VARSETTLE",
|
"VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_MOVE", "EDGE_STD",
|
||||||
"VERTEX_MOVE", "EDGE_STD", "EDGE_COMBOCUT", "EDGE_PRECUT",
|
"EDGE_COMBOCUT", "EDGE_PRECUT", "EDGE_POSTCUT", "_ENUM_END"};
|
||||||
"EDGE_POSTCUT", "_ENUM_END"};
|
|
||||||
return names[m_e];
|
return names[m_e];
|
||||||
}
|
}
|
||||||
enum en m_e;
|
enum en m_e;
|
||||||
|
|
@ -123,13 +117,11 @@ public:
|
||||||
class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex {
|
class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex {
|
||||||
AstScope* const m_scopep; // Scope the vertex is in
|
AstScope* const m_scopep; // Scope the vertex is in
|
||||||
AstSenTree* m_domainp; // Clock domain (nullptr = to be computed as we iterate)
|
AstSenTree* m_domainp; // Clock domain (nullptr = to be computed as we iterate)
|
||||||
bool m_isFromInput = false; // From input, or derived therefrom (conservatively false)
|
|
||||||
protected:
|
protected:
|
||||||
OrderEitherVertex(V3Graph* graphp, const OrderEitherVertex& old)
|
OrderEitherVertex(V3Graph* graphp, const OrderEitherVertex& old)
|
||||||
: V3GraphVertex{graphp, old}
|
: V3GraphVertex{graphp, old}
|
||||||
, m_scopep{old.m_scopep}
|
, m_scopep{old.m_scopep}
|
||||||
, m_domainp{old.m_domainp}
|
, m_domainp{old.m_domainp} {}
|
||||||
, m_isFromInput{old.m_isFromInput} {}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OrderEitherVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp)
|
OrderEitherVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp)
|
||||||
|
|
@ -146,43 +138,26 @@ public:
|
||||||
void domainp(AstSenTree* domainp) { m_domainp = domainp; }
|
void domainp(AstSenTree* domainp) { m_domainp = domainp; }
|
||||||
AstScope* scopep() const { return m_scopep; }
|
AstScope* scopep() const { return m_scopep; }
|
||||||
AstSenTree* domainp() const { return m_domainp; }
|
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 {
|
class OrderLogicVertex final : public OrderEitherVertex {
|
||||||
AstNode* const m_nodep;
|
AstNode* const m_nodep;
|
||||||
|
AstSenTree* const m_hybridp;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
OrderLogicVertex(V3Graph* graphp, const OrderLogicVertex& old)
|
OrderLogicVertex(V3Graph* graphp, const OrderLogicVertex& old)
|
||||||
: OrderEitherVertex{graphp, old}
|
: OrderEitherVertex{graphp, old}
|
||||||
, m_nodep{old.m_nodep} {}
|
, m_nodep{old.m_nodep}
|
||||||
|
, m_hybridp{old.m_hybridp} {}
|
||||||
|
|
||||||
public:
|
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}
|
: 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() override = default;
|
||||||
virtual OrderLogicVertex* clone(V3Graph* graphp) const override {
|
virtual OrderLogicVertex* clone(V3Graph* graphp) const override {
|
||||||
return new OrderLogicVertex(graphp, *this);
|
return new OrderLogicVertex(graphp, *this);
|
||||||
|
|
@ -194,6 +169,7 @@ public:
|
||||||
return (cvtToHex(m_nodep) + "\\n " + cvtToStr(nodep()->typeName()));
|
return (cvtToHex(m_nodep) + "\\n " + cvtToStr(nodep()->typeName()));
|
||||||
}
|
}
|
||||||
AstNode* nodep() const { return m_nodep; }
|
AstNode* nodep() const { return m_nodep; }
|
||||||
|
AstSenTree* hybridp() const { return m_hybridp; }
|
||||||
virtual string dotShape() const override {
|
virtual string dotShape() const override {
|
||||||
return VN_IS(m_nodep, Active) ? "doubleoctagon" : "rect";
|
return VN_IS(m_nodep, Active) ? "doubleoctagon" : "rect";
|
||||||
}
|
}
|
||||||
|
|
@ -201,14 +177,11 @@ public:
|
||||||
|
|
||||||
class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex {
|
class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex {
|
||||||
AstVarScope* const m_varScp;
|
AstVarScope* const m_varScp;
|
||||||
bool m_isClock = false; // Used as clock
|
|
||||||
bool m_isDelayed = false; // Set in a delayed assignment
|
|
||||||
protected:
|
protected:
|
||||||
OrderVarVertex(V3Graph* graphp, const OrderVarVertex& old)
|
OrderVarVertex(V3Graph* graphp, const OrderVarVertex& old)
|
||||||
: OrderEitherVertex{graphp, old}
|
: OrderEitherVertex{graphp, old}
|
||||||
, m_varScp{old.m_varScp}
|
, m_varScp{old.m_varScp} {}
|
||||||
, m_isClock{old.m_isClock}
|
|
||||||
, m_isDelayed{old.m_isDelayed} {}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
|
OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp)
|
||||||
|
|
@ -220,10 +193,6 @@ public:
|
||||||
virtual FileLine* fileline() const override { return varScp()->fileline(); }
|
virtual FileLine* fileline() const override { return varScp()->fileline(); }
|
||||||
// ACCESSORS
|
// ACCESSORS
|
||||||
AstVarScope* varScp() const { return m_varScp; }
|
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"; }
|
virtual string dotShape() const override { return "ellipse"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -445,35 +414,6 @@ public:
|
||||||
V3GraphVertex* top) const override {
|
V3GraphVertex* top) const override {
|
||||||
return new OrderEdge(graphp, fromp, top, *this);
|
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 {
|
class OrderPostCutEdge final : public OrderEdge {
|
||||||
|
|
@ -494,7 +434,6 @@ public:
|
||||||
return new OrderPostCutEdge(graphp, fromp, top, *this);
|
return new OrderPostCutEdge(graphp, fromp, top, *this);
|
||||||
}
|
}
|
||||||
virtual string dotColor() const override { return "PaleGreen"; }
|
virtual string dotColor() const override { return "PaleGreen"; }
|
||||||
virtual bool followComboConnected() const override { return false; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class OrderPreCutEdge final : public OrderEdge {
|
class OrderPreCutEdge final : public OrderEdge {
|
||||||
|
|
@ -515,7 +454,6 @@ public:
|
||||||
}
|
}
|
||||||
virtual ~OrderPreCutEdge() override = default;
|
virtual ~OrderPreCutEdge() override = default;
|
||||||
virtual string dotColor() const override { return "khaki"; }
|
virtual string dotColor() const override { return "khaki"; }
|
||||||
virtual bool followComboConnected() const override { return false; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // Guard
|
#endif // Guard
|
||||||
|
|
|
||||||
|
|
@ -1841,7 +1841,6 @@ private:
|
||||||
for (V3GraphEdge* edgep = (*ovvIt)->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
for (V3GraphEdge* edgep = (*ovvIt)->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||||
const OrderLogicVertex* const logicp = dynamic_cast<OrderLogicVertex*>(edgep->fromp());
|
const OrderLogicVertex* const logicp = dynamic_cast<OrderLogicVertex*>(edgep->fromp());
|
||||||
if (!logicp) continue;
|
if (!logicp) continue;
|
||||||
if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue;
|
|
||||||
LogicMTask* const writerMtaskp = m_olv2mtask.at(logicp);
|
LogicMTask* const writerMtaskp = m_olv2mtask.at(logicp);
|
||||||
(*tasksByRankp)[writerMtaskp->rank()].insert(writerMtaskp);
|
(*tasksByRankp)[writerMtaskp->rank()].insert(writerMtaskp);
|
||||||
}
|
}
|
||||||
|
|
@ -1849,7 +1848,6 @@ private:
|
||||||
for (V3GraphEdge* edgep = (*ovvIt)->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
for (V3GraphEdge* edgep = (*ovvIt)->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||||
const OrderLogicVertex* const logicp = dynamic_cast<OrderLogicVertex*>(edgep->fromp());
|
const OrderLogicVertex* const logicp = dynamic_cast<OrderLogicVertex*>(edgep->fromp());
|
||||||
if (!logicp) continue;
|
if (!logicp) continue;
|
||||||
if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue;
|
|
||||||
LogicMTask* const readerMtaskp = m_olv2mtask.at(logicp);
|
LogicMTask* const readerMtaskp = m_olv2mtask.at(logicp);
|
||||||
(*tasksByRankp)[readerMtaskp->rank()].insert(readerMtaskp);
|
(*tasksByRankp)[readerMtaskp->rank()].insert(readerMtaskp);
|
||||||
}
|
}
|
||||||
|
|
@ -2563,6 +2561,11 @@ void V3Partition::go(V3Graph* mtasksp) {
|
||||||
// (and the logic nodes therein.)
|
// (and the logic nodes therein.)
|
||||||
uint32_t totalGraphCost = 0;
|
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
|
// The V3InstrCount within LogicMTask will set user5 on each AST
|
||||||
// node, to assert that we never count any node twice.
|
// node, to assert that we never count any node twice.
|
||||||
const VNUser5InUse inUser5;
|
const VNUser5InUse inUser5;
|
||||||
|
|
@ -2578,8 +2581,21 @@ void V3Partition::go(V3Graph* mtasksp) {
|
||||||
totalGraphCost += mtaskp->cost();
|
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
|
// Create the mtask->mtask dep edges based on vertex deps
|
||||||
setupMTaskDeps(mtasksp, &vx2mtask);
|
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");
|
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
|
// STATE
|
||||||
AstTopScope* const m_topScopep; // Top scope to add global SenTrees to
|
AstTopScope* const m_topScopep; // Top scope to add global SenTrees to
|
||||||
std::unordered_set<VNRef<AstSenTree>> m_trees; // Set of global SenTrees
|
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);
|
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:
|
public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
SenTreeFinder()
|
SenTreeFinder()
|
||||||
|
|
@ -50,6 +61,8 @@ public:
|
||||||
for (AstSenTree* senTreep = m_topScopep->senTreesp(); senTreep;
|
for (AstSenTree* senTreep = m_topScopep->senTreesp(); senTreep;
|
||||||
senTreep = VN_AS(senTreep->nextp(), SenTree)) {
|
senTreep = VN_AS(senTreep->nextp(), SenTree)) {
|
||||||
m_trees.emplace(*senTreep);
|
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.
|
// Return the global combinational AstSenTree.
|
||||||
// If no such global SenTree exists create one and add it to the stored AstTopScope.
|
// If no such global SenTree exists create one and add it to the stored AstTopScope.
|
||||||
AstSenTree* getComb() {
|
AstSenTree* getComb() {
|
||||||
FileLine* const fl = m_topScopep->fileline();
|
if (!m_combop) m_combop = makeUnique<AstSenItem::Combo>();
|
||||||
AstSenTree* const combp = new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Combo()}};
|
return m_combop;
|
||||||
AstSenTree* const resultp = getSenTree(combp);
|
}
|
||||||
VL_DO_DANGLING(combp->deleteTree(), combp); // getSenTree clones, so can delete
|
|
||||||
return resultp;
|
// 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* makeDpiExporTrigger() {
|
||||||
AstVarScope* dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp();
|
AstNetlist* const netlistp = v3Global.rootp();
|
||||||
|
AstVarScope* dpiExportTriggerp = netlistp->dpiExportTriggerp();
|
||||||
if (!dpiExportTriggerp) {
|
if (!dpiExportTriggerp) {
|
||||||
// Create the global DPI export trigger flag the first time we encounter a DPI export.
|
// 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.
|
// This flag is set any time a DPI export is invoked, and cleared at the end of eval.
|
||||||
FileLine* const fl = m_topScopep->fileline();
|
FileLine* const fl = m_topScopep->fileline();
|
||||||
AstVar* const varp
|
const string name{"__Vdpi_export_trigger"};
|
||||||
= new AstVar{fl, VVarType::VAR, "__Vdpi_export_trigger", VFlagBitPacked{}, 1};
|
AstVar* const varp = new AstVar{fl, VVarType::VAR, name, VFlagBitPacked{}, 1};
|
||||||
m_topScopep->scopep()->modp()->addStmtp(varp);
|
m_topScopep->scopep()->modp()->addStmtp(varp);
|
||||||
dpiExportTriggerp = new AstVarScope{fl, m_topScopep->scopep(), varp};
|
dpiExportTriggerp = new AstVarScope{fl, m_topScopep->scopep(), varp};
|
||||||
m_topScopep->scopep()->addVarp(dpiExportTriggerp);
|
m_topScopep->scopep()->addVarp(dpiExportTriggerp);
|
||||||
v3Global.rootp()->dpiExportTriggerp(dpiExportTriggerp);
|
netlistp->dpiExportTriggerp(dpiExportTriggerp);
|
||||||
}
|
}
|
||||||
return dpiExportTriggerp;
|
return dpiExportTriggerp;
|
||||||
}
|
}
|
||||||
|
|
@ -1309,7 +1310,7 @@ private:
|
||||||
AstAlways* const alwaysp = new AstAlways{
|
AstAlways* const alwaysp = new AstAlways{
|
||||||
fl, VAlwaysKwd::ALWAYS,
|
fl, VAlwaysKwd::ALWAYS,
|
||||||
new AstSenTree{
|
new AstSenTree{
|
||||||
fl, new AstSenItem{fl, VEdgeType::ET_HIGHEDGE,
|
fl, new AstSenItem{fl, VEdgeType::ET_DPIEXPORT,
|
||||||
new AstVarRef{fl, dpiExportTriggerp, VAccess::READ}}},
|
new AstVarRef{fl, dpiExportTriggerp, VAccess::READ}}},
|
||||||
nullptr};
|
nullptr};
|
||||||
for (AstVarScope* const varScopep : writtenps) {
|
for (AstVarScope* const varScopep : writtenps) {
|
||||||
|
|
|
||||||
|
|
@ -2519,7 +2519,7 @@ private:
|
||||||
methodCallClass(nodep, adtypep);
|
methodCallClass(nodep, adtypep);
|
||||||
} else if (AstUnpackArrayDType* const adtypep = VN_CAST(fromDtp, UnpackArrayDType)) {
|
} else if (AstUnpackArrayDType* const adtypep = VN_CAST(fromDtp, UnpackArrayDType)) {
|
||||||
methodCallUnpack(nodep, adtypep);
|
methodCallUnpack(nodep, adtypep);
|
||||||
} else if (basicp && basicp->isEventValue()) {
|
} else if (basicp && basicp->isEvent()) {
|
||||||
methodCallEvent(nodep, basicp);
|
methodCallEvent(nodep, basicp);
|
||||||
} else if (basicp && basicp->isString()) {
|
} else if (basicp && basicp->isString()) {
|
||||||
methodCallString(nodep, basicp);
|
methodCallString(nodep, basicp);
|
||||||
|
|
@ -3119,10 +3119,12 @@ private:
|
||||||
void methodCallEvent(AstMethodCall* nodep, AstBasicDType*) {
|
void methodCallEvent(AstMethodCall* nodep, AstBasicDType*) {
|
||||||
// Method call on event
|
// Method call on event
|
||||||
if (nodep->name() == "triggered") {
|
if (nodep->name() == "triggered") {
|
||||||
// We represent events as numbers, so can just return number
|
|
||||||
methodOkArguments(nodep, 0, 0);
|
methodOkArguments(nodep, 0, 0);
|
||||||
AstNode* const newp = nodep->fromp()->unlinkFrBack();
|
AstCMethodHard* const callp = new AstCMethodHard{
|
||||||
nodep->replaceWith(newp);
|
nodep->fileline(), nodep->fromp()->unlinkFrBack(), "isTriggered"};
|
||||||
|
callp->dtypeSetBit();
|
||||||
|
callp->pure(true);
|
||||||
|
nodep->replaceWith(callp);
|
||||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||||
} else {
|
} else {
|
||||||
nodep->v3error("Unknown built-in event method " << nodep->prettyNameQ());
|
nodep->v3error("Unknown built-in event method " << nodep->prettyNameQ());
|
||||||
|
|
@ -3979,7 +3981,7 @@ private:
|
||||||
// if (debug()) nodep->dumpTree(cout, " AssignOut: ");
|
// if (debug()) nodep->dumpTree(cout, " AssignOut: ");
|
||||||
}
|
}
|
||||||
if (const AstBasicDType* const basicp = nodep->rhsp()->dtypep()->basicp()) {
|
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
|
// see t_event_copy.v for commentary on the mess involved
|
||||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type");
|
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type");
|
||||||
}
|
}
|
||||||
|
|
@ -4783,6 +4785,9 @@ private:
|
||||||
userIterateChildren(nodep, nullptr);
|
userIterateChildren(nodep, nullptr);
|
||||||
m_procedurep = nullptr;
|
m_procedurep = nullptr;
|
||||||
}
|
}
|
||||||
|
virtual void visit(AstSenItem* nodep) override {
|
||||||
|
userIterateChildren(nodep, WidthVP(SELF, BOTH).p());
|
||||||
|
}
|
||||||
virtual void visit(AstWith* nodep) override {
|
virtual void visit(AstWith* nodep) override {
|
||||||
// Should otherwise be underneath a method call
|
// Should otherwise be underneath a method call
|
||||||
AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp();
|
AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp();
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
#include "V3Case.h"
|
#include "V3Case.h"
|
||||||
#include "V3Cast.h"
|
#include "V3Cast.h"
|
||||||
#include "V3Cdc.h"
|
#include "V3Cdc.h"
|
||||||
#include "V3Changed.h"
|
|
||||||
#include "V3Class.h"
|
#include "V3Class.h"
|
||||||
#include "V3Clean.h"
|
#include "V3Clean.h"
|
||||||
#include "V3Clock.h"
|
#include "V3Clock.h"
|
||||||
|
|
@ -53,7 +52,6 @@
|
||||||
#include "V3File.h"
|
#include "V3File.h"
|
||||||
#include "V3Force.h"
|
#include "V3Force.h"
|
||||||
#include "V3Gate.h"
|
#include "V3Gate.h"
|
||||||
#include "V3GenClk.h"
|
|
||||||
#include "V3Graph.h"
|
#include "V3Graph.h"
|
||||||
#include "V3HierBlock.h"
|
#include "V3HierBlock.h"
|
||||||
#include "V3Inline.h"
|
#include "V3Inline.h"
|
||||||
|
|
@ -70,7 +68,6 @@
|
||||||
#include "V3Localize.h"
|
#include "V3Localize.h"
|
||||||
#include "V3MergeCond.h"
|
#include "V3MergeCond.h"
|
||||||
#include "V3Name.h"
|
#include "V3Name.h"
|
||||||
#include "V3Order.h"
|
|
||||||
#include "V3Os.h"
|
#include "V3Os.h"
|
||||||
#include "V3Param.h"
|
#include "V3Param.h"
|
||||||
#include "V3ParseSym.h"
|
#include "V3ParseSym.h"
|
||||||
|
|
@ -80,6 +77,7 @@
|
||||||
#include "V3ProtectLib.h"
|
#include "V3ProtectLib.h"
|
||||||
#include "V3Randomize.h"
|
#include "V3Randomize.h"
|
||||||
#include "V3Reloop.h"
|
#include "V3Reloop.h"
|
||||||
|
#include "V3Sched.h"
|
||||||
#include "V3Scope.h"
|
#include "V3Scope.h"
|
||||||
#include "V3Scoreboard.h"
|
#include "V3Scoreboard.h"
|
||||||
#include "V3Slice.h"
|
#include "V3Slice.h"
|
||||||
|
|
@ -376,11 +374,8 @@ static void process() {
|
||||||
|
|
||||||
if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "PreOrder");
|
if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "PreOrder");
|
||||||
|
|
||||||
// Order the code; form SBLOCKs and BLOCKCALLs
|
// Schedule the logic
|
||||||
V3Order::orderAll(v3Global.rootp());
|
V3Sched::schedule(v3Global.rootp());
|
||||||
|
|
||||||
// Change generated clocks to look at delayed signals
|
|
||||||
V3GenClk::genClkAll(v3Global.rootp());
|
|
||||||
|
|
||||||
// Convert sense lists into IF statements.
|
// Convert sense lists into IF statements.
|
||||||
V3Clock::clockAll(v3Global.rootp());
|
V3Clock::clockAll(v3Global.rootp());
|
||||||
|
|
@ -392,15 +387,13 @@ static void process() {
|
||||||
V3Const::constifyAll(v3Global.rootp());
|
V3Const::constifyAll(v3Global.rootp());
|
||||||
V3Life::lifeAll(v3Global.rootp());
|
V3Life::lifeAll(v3Global.rootp());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v3Global.opt.oLifePost()) V3LifePost::lifepostAll(v3Global.rootp());
|
if (v3Global.opt.oLifePost()) V3LifePost::lifepostAll(v3Global.rootp());
|
||||||
|
|
||||||
// Remove unused vars
|
// Remove unused vars
|
||||||
V3Const::constifyAll(v3Global.rootp());
|
V3Const::constifyAll(v3Global.rootp());
|
||||||
V3Dead::deadifyAllScoped(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
|
// 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
|
// Note past this point, we presume traced variables won't move between CFuncs
|
||||||
// (It's OK if untraced temporaries move around, or vars
|
// (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(),
|
{ $$ = new AstDefImplicitDType{$1->fileline(),
|
||||||
"__typeimpenum" + cvtToStr(GRAMMARP->s_modTypeImpNum++),
|
"__typeimpenum" + cvtToStr(GRAMMARP->s_modTypeImpNum++),
|
||||||
SYMP, VFlagChildDType{}, $1}; }
|
SYMP, VFlagChildDType{}, $1}; }
|
||||||
| ySTRING { $$ = new AstBasicDType($1,VBasicDTypeKwd::STRING); }
|
| ySTRING
|
||||||
| yCHANDLE { $$ = new AstBasicDType($1,VBasicDTypeKwd::CHANDLE); }
|
{ $$ = new AstBasicDType{$1, VBasicDTypeKwd::STRING}; }
|
||||||
| yEVENT { $$ = new AstBasicDType($1,VBasicDTypeKwd::EVENTVALUE); }
|
| yCHANDLE
|
||||||
|
{ $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; }
|
||||||
|
| yEVENT
|
||||||
|
{ $$ = new AstBasicDType{$1, VBasicDTypeKwd::EVENT}; v3Global.setHasEvents(); }
|
||||||
// // Rules overlap virtual_interface_declaration
|
// // Rules overlap virtual_interface_declaration
|
||||||
// // Parameters here are SV2009
|
// // Parameters here are SV2009
|
||||||
// // IEEE has ['.' modport] but that will conflict with port
|
// // IEEE has ['.' modport] but that will conflict with port
|
||||||
|
|
@ -2999,7 +3002,7 @@ senitem<senItemp>: // IEEE: part of event_expression, non-'OR' ','
|
||||||
;
|
;
|
||||||
|
|
||||||
senitemVar<senItemp>:
|
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
|
senitemEdge<senItemp>: // IEEE: part of event_expression
|
||||||
|
|
@ -3221,13 +3224,11 @@ statement_item<nodep>: // IEEE: statement_item
|
||||||
| yDISABLE yFORK ';' { $$ = new AstDisableFork($1); }
|
| yDISABLE yFORK ';' { $$ = new AstDisableFork($1); }
|
||||||
// // IEEE: event_trigger
|
// // IEEE: event_trigger
|
||||||
| yP_MINUSGT idDotted/*hierarchical_identifier-event*/ ';'
|
| yP_MINUSGT idDotted/*hierarchical_identifier-event*/ ';'
|
||||||
{ // AssignDly because we don't have stratified queue, and need to
|
{ $$ = new AstFireEvent{$1, $2, false}; }
|
||||||
// read events, clear next event, THEN apply this set
|
|
||||||
$$ = new AstAssignDly($1, $2, new AstConst($1, AstConst::BitTrue())); }
|
|
||||||
//UNSUP yP_MINUSGTGT delay_or_event_controlE hierarchical_identifier/*event*/ ';' { UNSUP }
|
//UNSUP yP_MINUSGTGT delay_or_event_controlE hierarchical_identifier/*event*/ ';' { UNSUP }
|
||||||
// // IEEE remove below
|
// // IEEE remove below
|
||||||
| yP_MINUSGTGT delayE idDotted/*hierarchical_identifier-event*/ ';'
|
| yP_MINUSGTGT delayE idDotted/*hierarchical_identifier-event*/ ';'
|
||||||
{ $$ = new AstAssignDly($1, $3, new AstConst($1, AstConst::BitTrue())); }
|
{ $$ = new AstFireEvent{$1, $3, true}; }
|
||||||
//
|
//
|
||||||
// // IEEE: loop_statement
|
// // IEEE: loop_statement
|
||||||
| yFOREVER stmtBlock { $$ = new AstWhile($1,new AstConst($1, AstConst::BitTrue()), $2); }
|
| 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);
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
verilator_flags2 => ["-Wno-CLKDATA"],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ compile(
|
||||||
|
|
||||||
if ($Self->{vlt_all}) {
|
if ($Self->{vlt_all}) {
|
||||||
file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 10);
|
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(
|
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;
|
input clk;
|
||||||
|
|
||||||
// verilator lint_off GENCLK
|
|
||||||
|
|
||||||
reg [7:0] cyc; initial cyc = 0;
|
reg [7:0] cyc; initial cyc = 0;
|
||||||
reg genclk;
|
reg genclk;
|
||||||
// verilator lint_off MULTIDRIVEN
|
// verilator lint_off MULTIDRIVEN
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ module t (/*AUTOARG*/
|
||||||
|
|
||||||
input clk;
|
input clk;
|
||||||
|
|
||||||
// verilator lint_off GENCLK
|
|
||||||
|
|
||||||
reg [7:0] cyc; initial cyc = 0;
|
reg [7:0] cyc; initial cyc = 0;
|
||||||
reg [7:0] padd;
|
reg [7:0] padd;
|
||||||
reg dsp_ph1, dsp_ph2, dsp_reset;
|
reg dsp_ph1, dsp_ph2, dsp_reset;
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,8 @@ module t_clk (/*AUTOARG*/
|
||||||
// verilator lint_on MULTIDRIVEN
|
// verilator lint_on MULTIDRIVEN
|
||||||
reg [7:0] int_clocks_copy;
|
reg [7:0] int_clocks_copy;
|
||||||
|
|
||||||
// verilator lint_off GENCLK
|
|
||||||
reg internal_clk; initial internal_clk = 0;
|
reg internal_clk; initial internal_clk = 0;
|
||||||
reg reset_int_;
|
reg reset_int_;
|
||||||
// verilator lint_on GENCLK
|
|
||||||
|
|
||||||
always @ (posedge clk) begin
|
always @ (posedge clk) begin
|
||||||
`ifdef TEST_VERBOSE
|
`ifdef TEST_VERBOSE
|
||||||
|
|
@ -170,9 +168,7 @@ module t_clk_two (/*AUTOARG*/
|
||||||
);
|
);
|
||||||
input fastclk;
|
input fastclk;
|
||||||
input reset_l;
|
input reset_l;
|
||||||
// verilator lint_off GENCLK
|
|
||||||
reg clk2;
|
reg clk2;
|
||||||
// verilator lint_on GENCLK
|
|
||||||
reg [31:0] count;
|
reg [31:0] count;
|
||||||
|
|
||||||
t_clk_twob tb (.*);
|
t_clk_twob tb (.*);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ module t (/*AUTOARG*/
|
||||||
input clk;
|
input clk;
|
||||||
integer cyc; initial cyc=1;
|
integer cyc; initial cyc=1;
|
||||||
|
|
||||||
// verilator lint_off GENCLK
|
|
||||||
reg gendlyclk_r;
|
reg gendlyclk_r;
|
||||||
reg [31:0] gendlydata_r;
|
reg [31:0] gendlydata_r;
|
||||||
reg [31:0] dlydata_gr;
|
reg [31:0] dlydata_gr;
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||||
|
|
||||||
scenarios(simulator => 1);
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
my $fail = $Self->{vlt_all};
|
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
);
|
);
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
check_finished => !$fail,
|
check_finished => 1
|
||||||
fails => $fail,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ok(1);
|
ok(1);
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,14 @@ scenarios(simulator => 1);
|
||||||
|
|
||||||
top_filename("t/t_clk_latch.v");
|
top_filename("t/t_clk_latch.v");
|
||||||
|
|
||||||
my $fail = $Self->{vlt_all};
|
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
v_flags2 => ['+define+EDGE_DETECT_STYLE'],
|
v_flags2 => ['+define+EDGE_DETECT_STYLE'],
|
||||||
fails => $fail,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
check_finished => !$fail,
|
check_finished => 1
|
||||||
) if !$fail;
|
);
|
||||||
|
|
||||||
ok(1);
|
ok(1);
|
||||||
1;
|
1;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
`define GATED_CLK_TESTCASE 1
|
`define GATED_CLK_TESTCASE 1
|
||||||
|
|
||||||
// A side effect of the problem is this warning, disabled by default
|
// A side effect of the problem is this warning, disabled by default
|
||||||
//verilator lint_on IMPERFECTSCH
|
|
||||||
|
|
||||||
// Test Bench
|
// Test Bench
|
||||||
module t (/*AUTOARG*/
|
module t (/*AUTOARG*/
|
||||||
|
|
@ -153,7 +152,7 @@ module Test (/*AUTOARG*/
|
||||||
output wire [7:0] entry_vld;
|
output wire [7:0] entry_vld;
|
||||||
|
|
||||||
wire [7:0] gclk_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;
|
reg [7:0] flop_en_vld;
|
||||||
|
|
||||||
always @(posedge clk) flop_en_vld <= ff_en_e1;
|
always @(posedge clk) flop_en_vld <= ff_en_e1;
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ module t (/*AUTOARG*/
|
||||||
|
|
||||||
reg reset_l;
|
reg reset_l;
|
||||||
|
|
||||||
// verilator lint_off GENCLK
|
|
||||||
|
|
||||||
/*AUTOWIRE*/
|
/*AUTOWIRE*/
|
||||||
// Beginning of automatic wires (for undeclared instantiated-module outputs)
|
// Beginning of automatic wires (for undeclared instantiated-module outputs)
|
||||||
// End of automatics
|
// 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);
|
scenarios(linter => 1);
|
||||||
|
|
||||||
lint(
|
lint();
|
||||||
fails => 1,
|
|
||||||
expect_filename => $Self->{golden_filename},
|
|
||||||
);
|
|
||||||
|
|
||||||
ok(1);
|
ok(1);
|
||||||
1;
|
1;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||||
scenarios(simulator => 1);
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
verilator_flags2 => ["--trace", "-Wno-CLKDATA"]
|
verilator_flags2 => ["--trace"]
|
||||||
);
|
);
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,6 @@ module t (/*AUTOARG*/
|
||||||
assign clk_3 = {3{clk_1}};
|
assign clk_3 = {3{clk_1}};
|
||||||
assign clk_final = clk_3[0];
|
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 res8 = {clk_3, 1'b0, clk_4};
|
||||||
assign res16 = {count, clk_3, clk_1, 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
|
always @(posedge clk_final or negedge clk_final) begin
|
||||||
count = count + 1;
|
count = count + 1;
|
||||||
// the following assignment should trigger the CLKDATA warning
|
|
||||||
// because CLOCK signal is used as DATA in sequential block
|
|
||||||
res <= clk_final;
|
res <= clk_final;
|
||||||
if ( count == 8'hf) begin
|
if ( count == 8'hf) begin
|
||||||
$write("*-* All Finished *-*\n");
|
$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");
|
$display("stmt");
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
always @([any] in) begin
|
always @([changed] in) begin
|
||||||
begin
|
begin
|
||||||
$display("stmt");
|
$display("stmt");
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ scenarios(simulator => 1);
|
||||||
my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml";
|
my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml";
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
verilator_flags2 => ["--stats $Self->{t_dir}/t_dedupe_clk_gate.vlt"],
|
verilator_flags2 => ["--stats"],
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($Self->{vlt_all}) {
|
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);
|
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);
|
module clock_gate_flop (gated_clk, clk, clken);
|
||||||
output gated_clk;
|
output gated_clk;
|
||||||
input clk, clken;
|
input clk, clken;
|
||||||
reg clken_r /*verilator clock_enable*/;
|
reg clken_r;
|
||||||
assign gated_clk = clk & clken_r ;
|
assign gated_clk = clk & clken_r ;
|
||||||
|
|
||||||
always @(negedge clk)
|
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);
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
verilator_flags2 => ["-Wno-CLKDATA", "-Wno-UNOPTFLAT"]
|
verilator_flags2 => ["-Wno-UNOPTFLAT"]
|
||||||
);
|
);
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||||
scenarios(simulator => 1);
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
verilator_flags2 => ["-Wno-CLKDATA", "-Wno-UNOPTFLAT"]
|
verilator_flags2 => ["-Wno-UNOPTFLAT"]
|
||||||
);
|
);
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
|
|
|
||||||
|
|
@ -33,49 +33,44 @@ module t(/*AUTOARG*/
|
||||||
|
|
||||||
always @(e2) begin
|
always @(e2) begin
|
||||||
`WRITE_VERBOSE(("[%0t] e2\n", $time));
|
`WRITE_VERBOSE(("[%0t] e2\n", $time));
|
||||||
|
if (!e2.triggered) $stop;
|
||||||
last_event[2] = 1;
|
last_event[2] = 1;
|
||||||
end
|
end
|
||||||
|
|
||||||
always @(posedge clk) begin
|
always @(posedge clk) begin
|
||||||
`WRITE_VERBOSE(("[%0t] cyc=%0d last_event=%5b\n", $time, cyc, last_event));
|
`WRITE_VERBOSE(("[%0t] cyc=%0d last_event=%5b\n", $time, cyc, last_event));
|
||||||
cyc <= cyc + 1;
|
cyc <= cyc + 1;
|
||||||
if (cyc == 1) begin
|
case (cyc)
|
||||||
// Check no initial trigger
|
default: begin
|
||||||
if (last_event != 0) $stop;
|
// Check no initial or spurious trigger
|
||||||
end
|
if (last_event != 0) $stop;
|
||||||
//
|
end
|
||||||
else if (cyc == 10) begin
|
//
|
||||||
last_event = 0;
|
10: begin
|
||||||
-> e1;
|
if (last_event != 0) $stop;
|
||||||
end
|
-> e1;
|
||||||
else if (cyc == 12) begin
|
if (!e1.triggered) $stop;
|
||||||
if (last_event != 32'b10) $stop;
|
end
|
||||||
last_event = 0;
|
11: begin
|
||||||
end
|
if (last_event != 32'b10) $stop;
|
||||||
else if (cyc == 13) begin
|
last_event = 0;
|
||||||
// Check not still triggering
|
end
|
||||||
if (last_event != 0) $stop;
|
//
|
||||||
last_event = 0;
|
13: begin
|
||||||
end
|
if (last_event != 0) $stop;
|
||||||
//
|
->> e2;
|
||||||
else if (cyc == 10) begin
|
if (e2.triggered) $stop;
|
||||||
last_event = 0;
|
end
|
||||||
->> e2;
|
14: begin
|
||||||
end
|
if (last_event != 32'b100) $stop;
|
||||||
else if (cyc == 12) begin
|
last_event = 0;
|
||||||
if (last_event != 32'b100) $stop;
|
end
|
||||||
last_event = 0;
|
//
|
||||||
end
|
99: begin
|
||||||
else if (cyc == 13) begin
|
$write("*-* All Finished *-*\n");
|
||||||
// Check not still triggering
|
$finish;
|
||||||
if (last_event != 0) $stop;
|
end
|
||||||
last_event = 0;
|
endcase
|
||||||
end
|
|
||||||
//
|
|
||||||
else if (cyc == 99) begin
|
|
||||||
$write("*-* All Finished *-*\n");
|
|
||||||
$finish;
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ execute(
|
||||||
check_finished => 1,
|
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);
|
ok(1);
|
||||||
1;
|
1;
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,14 @@ sub check_evals {
|
||||||
local $/; undef $/;
|
local $/; undef $/;
|
||||||
my $wholefile = <$fh>;
|
my $wholefile = <$fh>;
|
||||||
|
|
||||||
if ($wholefile =~ /___eval__[0-9]+\(.*\)\s*{/) {
|
if ($wholefile =~ /__eval_nba__[0-9]+\(.*\)\s*{/) {
|
||||||
++$got;
|
++$got;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$got >= 2 or error("Too few _eval functions found: $got");
|
$got >= 2 or error("Too few _eval functions found: $got");
|
||||||
}
|
}
|
||||||
|
|
||||||
scenarios(vlt_all => 1);
|
scenarios(vlt => 1);
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
v_flags2 => ["--output-split 1 --output-split-cfuncs 20"],
|
v_flags2 => ["--output-split 1 --output-split-cfuncs 20"],
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ execute(
|
||||||
check_finished => 1,
|
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);
|
ok(1);
|
||||||
1;
|
1;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ module chk (input clk, input rst_l, input expr);
|
||||||
|
|
||||||
wire noxs = ((expr ^ expr) == 1'b0);
|
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;
|
reg hasx;
|
||||||
always @ (noxs) begin
|
always @ (noxs) begin
|
||||||
if (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'
|
%Error: t/t_fuzz_always_bad.v:10:19: Can't find definition of task/function: 'h'
|
||||||
10 | always @ c.a c: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
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||||
|
|
||||||
scenarios(simulator => 1);
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
|
# FIXMEV5: fix
|
||||||
|
skip("This test is racy, needs fixing");
|
||||||
|
|
||||||
compile(
|
compile(
|
||||||
nc_flags2 => ['+access+r'],
|
nc_flags2 => ['+access+r'],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||||
|
|
||||||
scenarios(simulator => 1);
|
scenarios(simulator => 1);
|
||||||
|
|
||||||
|
# FIXMEV5: fix
|
||||||
|
skip("This test is racy, needs fixing");
|
||||||
|
|
||||||
compile(
|
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[0].tag
|
||||||
created tag with scope = top.t.b.gen[1].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 scope = top.t
|
||||||
mod a has tag = top.t.tag
|
mod a has tag = top.t.tag
|
||||||
mod b has scope = top.t.b
|
mod b has scope = top.t.b
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,13 @@ module t (/*AUTOARG*/
|
||||||
`ifdef PROTLIB_TOP
|
`ifdef PROTLIB_TOP
|
||||||
secret i_secred(.clk(clk));
|
secret i_secred(.clk(clk));
|
||||||
`else
|
`else
|
||||||
|
/* verilator lint_off UNOPTFLAT */
|
||||||
wire [7:0] out0;
|
wire [7:0] out0;
|
||||||
wire [7:0] out1;
|
wire [7:0] out1;
|
||||||
wire [7:0] out2;
|
wire [7:0] out2;
|
||||||
/* verilator lint_off UNOPTFLAT */
|
|
||||||
wire [7:0] out3;
|
wire [7:0] out3;
|
||||||
wire [7:0] out3_2;
|
|
||||||
/* verilator lint_on UNOPTFLAT */
|
/* verilator lint_on UNOPTFLAT */
|
||||||
|
wire [7:0] out3_2;
|
||||||
wire [7:0] out5;
|
wire [7:0] out5;
|
||||||
wire [7:0] out6;
|
wire [7:0] out6;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ module t (/*AUTOARG*/
|
||||||
input clk;
|
input clk;
|
||||||
integer cyc; initial cyc=1;
|
integer cyc; initial cyc=1;
|
||||||
|
|
||||||
// verilator lint_on GENCLK
|
|
||||||
reg [31:0] long;
|
reg [31:0] long;
|
||||||
reg [63:0] quad;
|
reg [63:0] quad;
|
||||||
wire [31:0] longout;
|
wire [31:0] longout;
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ module t (/*AUTOARG*/
|
||||||
input clk;
|
input clk;
|
||||||
integer cyc; initial cyc=1;
|
integer cyc; initial cyc=1;
|
||||||
|
|
||||||
// verilator lint_off GENCLK
|
|
||||||
reg printclk;
|
reg printclk;
|
||||||
// verilator lint_on GENCLK
|
|
||||||
ps ps (printclk);
|
ps ps (printclk);
|
||||||
|
|
||||||
reg [7:0] a;
|
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